Generic constraints place restrictions on a generic type parameter. They are used to limit the kinds of types that can be used as arguments for the type parameter.
Basic Syntax:
class MyGeneric<T> where T : struct | class | new() | <base class name> | <interface name>
where T : structThe type argument T must be a value type. Any non-nullable value type can be specified.
(Value types include int, float, char, bool, struct, enum, byte, etc. Memory for a value type is allocated directly where it is declared.)
public class MyGeneric<T> where T : struct { }
static void Main(string[] args)
{
// Valid: int is a struct (value type)
MyGeneric<int> myGenericInt = new MyGeneric<int>();
}
where T : classThe type argument T must be a reference type. This includes any class, interface, delegate, or array type.
(Reference types include string and any custom class. When a reference type variable is declared, memory is allocated on the stack to hold an address. The actual object instance is created on the heap, and its memory address is stored in the stack variable.)
public class MyGeneric<T> where T : class { }
static void Main(string[] args)
{
// Valid: string is a class (reference type)
MyGeneric<string> myGenericString = new MyGeneric<string>();
}
where T : <interface name>The type argument T must implement the specified interface. Multiple interface constraints can be applied.
public interface IMyInterface
{
int Add(int a, int b);
void Show();
}
public class MyImplementation : IMyInterface
{
public int Add(int a, int b)
{
return a + b;
}
public void Show()
{
Console.WriteLine("This is a test.");
}
}
// T is constrained to types that implement IMyInterface
public class MyGeneric<T> where T : IMyInterface { }
static void Main(string[] args)
{
// Valid: MyImplementation implements IMyInterface
MyGeneric<MyImplementation> myGenericA = new MyGeneric<MyImplementation>();
}
where T : <base class name>The type argument T must be the specified base class or a class that inherits from it.
public class MyBaseClass { }
public class MyDerivedClass : MyBaseClass { }
// T is constrained to MyBaseClass or its subclasses
public class MyGeneric<T> where T : MyBaseClass { }
static void Main(string[] args)
{
// Valid: MyBaseClass is the specified class
MyGeneric<MyBaseClass> myGenericA = new MyGeneric<MyBaseClass>();
// Valid: MyDerivedClass inherits from MyBaseClass
MyGeneric<MyDerivedClass> myGenericB = new MyGeneric<MyDerivedClass>();
}
where T : new()The type argument T must have a public, parameterless constructor.
When used with other constraints, the new() constraint must always be specified last.
public class MyClass { }
// T is constrained to MyClass (and its subclasses) and must have a public parameterless constructor
public class MyGeneric<T> where T : MyClass, new() { }
static void Main(string[] args)
{
// Valid: MyClass has an implicit public parameterless constructor
MyGeneric<MyClass> myGenericA = new MyGeneric<MyClass>();
}
A common advanced use of the base class constraint is to ensure that a generic type parameter in a base class is the derived class itself. This is often referred to as the Curiously Recurring Template Pattern (CRTP).
// T is constrained to be MyBaseGeneric<T> or a type that derives from it.
public class MyBaseGeneric<T> where T : MyBaseGeneric<T> { }
According to the constraint where T : MyBaseGeneric<T>, the type argument T must be a class that inherits from MyBaseGeneric<T>.
This creates a powerful pattern. When another class, MyGeneric, wants to inherit from MyBaseGeneric<T>, it is forced to use itself as the type argument.
// This is valid because MyGeneric is passed as the type argument T,
// and MyGeneric itself inherits from MyBaseGeneric<MyGeneric>.
public class MyGeneric : MyBaseGeneric<MyGeneric> { }
This technique creates a strong, compile-time link between a base class and its derived classes, allowing the base class to call methods on the derived class without casting.