I am looking at some code and I don't understand what a particular constraint means in the following class definition:
internal abstract class Entity<T> : Entity
where T : Entity<T>
{ ... }
I don't understand what this implies about parameter type T
.
Object, you'll apply constraints to the type parameter. For example, the base class constraint tells the compiler that only objects of this type or derived from this type will be used as type arguments. Once the compiler has this guarantee, it can allow methods of that type to be called in the generic class.
A constraint is a requirement that types used as type arguments must satisfy. For example, a constraint might be that the type argument must implement a certain interface or inherit from a specific class. Constraints are optional; not specifying a constraint on a parameter is equivalent to using a Object constraint.
The type parameter is a placeholder for a specific type that the client specifies when they create an instance of the generic type. A generic class cannot be used as-is because it is simply a blueprint for that type.
The where clause in a generic definition specifies constraints on the types that are used as arguments for type parameters in a generic type, method, delegate, or local function. Constraints can specify interfaces, base classes, or require a generic type to be a reference, value, or unmanaged type.
Although I commented, I'm going to stick my oar in because I want to also note on what the base type gets from this.
Simply: T
must inherit Entity<T>
.
It's a kind of self-referencing generic often used such that the base class can contain the derived class type (via T
) in methods and other areas. It simply avoids you having to cast stuff or use a base reference in derived types. It can be quite useful, though I rarely see it used in our code.
I will note that this does not mean the base class can suddenly access derived members. It can still only see the lowest known type defined by the constraints, if they exist. If no constraints exist, object
is the lowest known type. The benefit is from the perspective of the derived type and the cleanliness it grants on code that is pushed into a base class.
In your case, it would see Entity<T>
and Entity
members. This is the reason for constraints.
A standard usage would be something like:
public class Customer : Entity<Customer>
{
}
public abstract class Entity<T>
where T : Entity<T>
{
public T Clone(T entityToClone)
{
return default(T); // Clone code here, returns derived type.
}
}
// Grants you...
Customer clonedCustomer = currentCustomer.Clone();
// Instead of...
Customer clonedCustomer = (Customer)currentCustomer.Clone();
// Ignore ethical constraints on cloning customers and definitely do not tell your sales team that you can ;-)
This is similar to the "Curiously Recurring Template Pattern" (but it is NOT the same).
It can be used (among other things) to help constraint the parameter types of methods in a derived class to the same type as the derived class itself.
Here's an interesting blog post from Eric Lippert on this subject.
The main use of this is to force classes that derive from Entity<T>
to implement some method that accepts a parameter of the same type as the deriving class.
In the following code sample, we declare in the Entity<T>
class a method DoSomethingWithTheSameTypeAsMe()
which accepts a parameter of type T
.
Because of the generic constraint, this will force any classes that derive from Entity<T>
to implement a version of DoSomethingWithTheSameTypeAsMe()
which takes a parameter of the deriving class type.
This is of limited use, and it is very confusing to read, so I agree with Eric Lippert when he says you should avoid such code!
using System;
namespace ConsoleApplication1
{
internal class Program
{
private static void Main()
{
var test1 = new Derived1();
var test2 = new Derived2();
test1.DoSomethingWithTheSameTypeAsMe(test1);
test2.DoSomethingWithTheSameTypeAsMe(test2);
}
}
public class Entity
{
public string Hello()
{
return "Hello, World.";
}
}
public abstract class Entity<T>: Entity where T: Entity<T>
{
public abstract void DoSomethingWithTheSameTypeAsMe(T item);
}
public sealed class Derived1: Entity<Derived1>
{
// You are forced to implement DoSomethingWithTheSameTypeAsMe() with a param type "Derived1".
// (i.e. the parameter is the same type as 'this')
public override void DoSomethingWithTheSameTypeAsMe(Derived1 item)
{
Console.WriteLine("Doing something with a Derived1 item: " + item.Hello());
}
}
public sealed class Derived2: Entity<Derived2>
{
public override void DoSomethingWithTheSameTypeAsMe(Derived2 item)
{
Console.WriteLine("Doing something with a Derived2 item: " + item.Hello());
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With