问题
I thought I understood generic constraints until I ran across this.
public class DBase<T> : DbContext, IDisposable where T : DBase<T>
How can T be DBase<T>
?
And if it can, what does it mean?
This code compiles and runs fine. I'm not fixing a problem. I just don't understand it.
It is used here
public class ChildDb : DBase<ChildDb>
Which, again, doesn't compute for me. It passes itself as a type parameter?
回答1:
How can
T
beDBase<T>
?
There is no limitation that prevents a Generic Parameter from deriving from itself. While it's not directly understandable with the example you've given. What about a Vertex / Vertice?
Excerpt from Wikipedia:
In geometry, a vertex (plural: vertices or vertexes) is a point where two or more curves, lines, or edges meet. As a consequence of this definition, the point where two lines meet to form an angle and the corners of polygons and polyhedra are vertices.1
How does one describe a Vertex (a point)?
// very simplified example
public class Vertex
{
public int X { get; set; }
public int Y { get; set; }
}
Now how do we add a collection of relationed Verticies to this class but only allow things that derive from this class?
public class Vertex<TVertex> : Vertex
where TVertex : Vertex<TVertex>
{
public IEnumerable<TVertex> Vertices { get; set; }
}
It a generic version of say:
public Vertex2
{
public IENumerable<Vertex2> Vertices { get; set; }
}
However when I derive from Vertex2, my Vertices
will always have to be IEnumerable<Vertex2>
, and the correct way to allow Vertices to be a derived class is to use this type of self-reference generic.
I'm sorry Erik, I lost the point in the details. What have I gained by the recursion?
Using Vertex2
, our derived types lose access to other derived properties:
public class MyVertex2: Vertex2
{
public int Id { get; set; }
}
so
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;
Sure you could cast it, but then you're casting it everywhere (that's not DRY)... and what if it's not a MyVertex (MullReferencesException or 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();
each time we navigate to a vertices we get the correct derived type, not the base class.
回答2:
John Wu posted a great blog in the comments, the TLDR of which is:
This code pattern allows you to declare a superclass that must be extended (possibly not by you, if you're writing a library that other people will use) in order to be used, but can have a bunch of methods/signatures (written by you) that return T when you write them but in practice will return objects of the child type (not written by you/you cannot know) so they can be used in a chained fashion (like the way most StringBuilder methods return the StringBuilder itself so the user can call .Append().AppendLine() ) without needing to be cast (in the code not written by you) from the parent type (written by you) to the child type (not written by you)
There's a caveat: it's not particularly useful because only the deepest child in an inheritance tree can be instantiated. Avoid using it
回答3:
As a useful example, it allows you to have some methods or properties in the base class which return derived type.
For example, in the fluent builders which have chainable methods, let's say we have a base builder which set some common properties. What should be the output type of these methods?
See the following example:
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;
}
}
Without having ControlBuilder<TBuilder, TControl>
as a constraint for TBuilder
, how you can return a TBuilder
from Id
method?
If you say ask why not return ControlBuilder<TBuilder, TControl>
, because if you return it, after calling .Id("something") in method chains, it will not show derived class methods and it just will show methods of ControlBuilder<TBuilder, TControl>
.
Let's say we create a TextBoxBuilder
for building a 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;
}
}
Now we can use it as expected:
var txt = TextBoxBuilder.With().Id("textBox1").Text("Hello!").Build();
来源:https://stackoverflow.com/questions/53983914/recursive-constraints-what-does-dbaset-where-t-dbaset-mean