递归约束:DBase <t>是什么:T:DBase <t>是什么意思?

时间:2018-12-31 05:29:09

标签: c# generics constraints

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

public class DBase<T> : DbContext, IDisposable where T : DBase<T>

如何成为DBase<T>
如果可以的话,这是什么意思?
这段代码可以编译并正常运行。我没有解决问题。我只是不明白。

在这里使用

    public class ChildDb : DBase<ChildDb>

再一次,这不适合我。它本身作为类型参数传递吗?

3 个答案:

答案 0 :(得分:3)

  

T如何成为DBase<T>

没有限制可以防止从其自身派生通用参数。虽然您所提供的示例无法直接理解。 Vertex / Vertice呢?

摘录自维基百科:

enter image description here

  

在几何中,顶点(复数:顶点或顶点)是两个或更多曲线,直线或边相交的点。根据此定义,两条线相交形成角度的点以及多边形和多面体的角均为顶点。1

如何描述一个顶点(一个点)?

// very simplified example
public class Vertex
{
  public int X { get; set; }
  public int Y { get; set; }
}

现在我们如何向该类添加一组相关的Verticies,但仅允许从该类派生的内容?

public class Vertex<TVertex> : Vertex
  where TVertex : Vertex<TVertex>
{
  public IEnumerable<TVertex> Vertices { get; set; }
}

它是通用的说法:

public Vertex2
{
  public IENumerable<Vertex2> Vertices { get; set; }
}

但是,当我从Vertex2派生时,我的Vertices将始终必须为IEnumerable<Vertex2>,并且允许Vertices成为派生类的正确方法是使用这种类型的自引用泛型。

  

对不起Erik,我在细节上没讲清楚。递归给我带来了什么?

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

public class MyVertex2: Vertex2
{
  public int Id { get; set; }
}

如此

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;

确定可以投射它,然后将其投射到任何地方(不是DRY)……如果它不是MyVertex(MullReferencesException或InvalidCastException),该怎么办。

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();

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

答案 1 :(得分:1)

John Wu在评论中发布了一个很棒的博客,其TLDR为:

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

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

答案 2 :(得分:1)

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

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

请参见以下示例:

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约束的情况下,如何从TBuilder方法返回Id

如果说问为什么不返回ControlBuilder<TBuilder, TControl>,因为如果返回它,则在方法链中调用.Id(“ something”)之后,它将不会显示派生类方法,而只会显示{ {1}}。

比方说,我们创建了一个ControlBuilder<TBuilder, TControl>用于构建TextBoxBuilder

TextBox

现在我们可以按预期使用它了:

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;
    }
}