首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >递归约束: DBase<T>是什么:T: DBase<T>意味着什么?

递归约束: DBase<T>是什么:T: DBase<T>意味着什么?
EN

Stack Overflow用户
提问于 2018-12-31 05:29:08
回答 3查看 161关注 0票数 3

我以为我理解了通用约束,直到我遇到了这个问题。

代码语言:javascript
复制
public class DBase<T> : DbContext, IDisposable where T : DBase<T>

T怎么可能是DBase<T>

如果可以的话,这意味着什么?

此代码编译并运行良好。我不是在解决问题。我只是不明白。

这里用的是

代码语言:javascript
复制
    public class ChildDb : DBase<ChildDb>

这也不能为我计算。它以类型参数的形式传递自己?

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2018-12-31 05:49:10

T怎么可能是DBase<T>

没有任何限制可以阻止泛型参数从自身派生出来。虽然你所举的例子并不能直接理解。顶点/顶点(https://en.wikipedia.org/wiki/Vertex_(geometry%29)?)呢?

摘自维基百科:

在几何学中,顶点(复数:顶点或顶点)是两个或多个曲线、线或边相交的点。作为这一定义的结果,两条线相交形成角的点和多边形和多面体的角是顶点。1

如何描述顶点(点)?

代码语言:javascript
复制
// very simplified example
public class Vertex
{
  public int X { get; set; }
  public int Y { get; set; }
}

现在,我们如何在这个类中添加一个关系版本集合,但只允许从这个类派生的东西呢?

代码语言:javascript
复制
public class Vertex<TVertex> : Vertex
  where TVertex : Vertex<TVertex>
{
  public IEnumerable<TVertex> Vertices { get; set; }
}

它是一种通用的说法:

代码语言:javascript
复制
public Vertex2
{
  public IENumerable<Vertex2> Vertices { get; set; }
}

然而,当我从Vertex2派生时,我的Vertices总是必须是IEnumerable<Vertex2>,允许顶点成为派生类的正确方法是使用这种类型的自引用泛型。

对不起,埃里克,我在细节上说错了。我从递归中得到了什么?

使用Vertex2,派生类型将失去对其他派生属性的访问:

代码语言:javascript
复制
public class MyVertex2: Vertex2
{
  public int Id { get; set; }
}

所以

代码语言:javascript
复制
var a = new MyVertex2 {Id = 1 };
var b = new MyVertex2 { Id = 2 };
a.Vertices = new List<Vertex2> { b };
b.Vertices = new List<Vertex2> { a };

// can't access Id because it's a Vertex2 not a MyVertex2
var bId = a.Vertices.First().Id;

当然,你可以投它,但是你把它投到任何地方(那不是干的).如果它不是MyVertex (MullReferencesException或InvalidCastException)呢?

代码语言:javascript
复制
public class MyVertex: Vertex<MyVertex>
{
  public int Id { get; set; }
}
var a = new MyVertex {Id = 1 };
var b = new MyVertex { Id = 2 };
a.Vertices = new List<MyVertex > { b };
b.Vertices = new List<MyVertex > { a };

var bId = a.Vertices.First().Id;
// or even
var aId = a.Vertices.First().Vertices.First();

每次导航到顶点时,我们都会得到正确的派生类型,而不是基类。

票数 3
EN

Stack Overflow用户

发布于 2018-12-31 06:52:54

吴宇森在评论中发表了一个很棒的博客,其中的TLDR是:

此代码模式允许您声明必须扩展的超类(如果您正在编写其他人将使用的库,则可能不是由您进行扩展)才能使用,但是可以有许多方法/签名(由您编写),它们在编写时返回T,但实际上将返回子类型的对象(不是由您/您无法知道),因此它们可以以链式方式使用(就像大多数StringBuilder方法返回StringBuilder本身的方式一样),这样用户就可以不需要从父类型(由您编写的代码)转换为子类型(不是由您编写的),就可以调用.Append().AppendLine()。

有一个警告:它不是特别有用,因为只有继承树中最深的子节点才能被实例化。避免使用它

票数 1
EN

Stack Overflow用户

发布于 2018-12-31 07:28:20

作为一个有用的示例,它允许您在基类中有一些返回派生类型的方法或属性。

例如,在具有可链式方法的fluent构建器中,假设我们有一个基本构建器,它设置了一些公共属性。这些方法的输出类型应该是什么?

请参见以下示例:

代码语言:javascript
复制
public abstract class Control
{
    public string Id { get; set; }
}
public abstract class ControlBuilder<TBuilder, TControl>
    where TBuilder : ControlBuilder<TBuilder, TControl>, new()
    where TControl : Control, new()
{
    protected TControl control;
    protected ControlBuilder()
    {
        control = new TControl();
    }
    public static TBuilder With()
    {
        return new TBuilder();
    }
    public TControl Build()
    {
        control;
    }
    public TBuilder Id(string id)
    {
        control.Id = id;
        return (TBuilder)this;
    }
}

如果没有ControlBuilder<TBuilder, TControl>作为TBuilder的约束,那么如何从Id方法返回TBuilder呢?

如果您说“为什么不返回ControlBuilder<TBuilder, TControl>”,因为如果您返回它,在方法链中调用.Id(“某某物”)之后,它将不会显示派生类方法,它只会显示ControlBuilder<TBuilder, TControl>的方法。

假设我们创建一个TextBoxBuilder来构建一个TextBox

代码语言:javascript
复制
public class TextBox : Control
{
    public string Text { get; set; }
}
public class TextBoxBuilder : ControlBuilder<TextBoxBuilder, TextBox>
{
    public TextBoxBuilder Text(string text)
    {
        control.Text = text;
        return this;
    }
}

现在我们可以像预期的那样使用它:

代码语言:javascript
复制
var txt = TextBoxBuilder.With().Id("textBox1").Text("Hello!").Build();
票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/53983914

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档