Constraints in Generics:

You have learned abut the generics in the previous section. Generics allow you to define a class with placeholders for the type of its fields, methods, parameters, etc. Consider the following example of a generic class.

Example: Generic class

class MyGenericClass<T>
{
    private T genericMemberVariable;

    public MyGenericClass(T value)
    {
        genericMemberVariable = value;
    }

    public T genericMethod(T genericParameter)
    {
        Console.WriteLine("Parameter type: {0}, value: {1}", typeof(T).ToString(),genericParameter);
        Console.WriteLine("Return type: {0}, value: {1}", typeof(T).ToString(), genericMemberVariable);
            
        return genericMemberVariable;
    }

    public T genericProperty { get; set; }
}

In the above example, the generic class MyGenericClass defines a placeholder for the type, but the placeholder is like a black box, because MyGenericClass doesn't know anything about the placeholder type, whether it is primitive or non-primitive type, or an interface or custom class etc.

C# includes Constraints to specify which type of placeholder type with the generic class is allowed. It will give a compile time error if you try to instantiate a generic class using a placeholder type that is not allowed by a constraints. For example, if the generic constraints specifies that only reference type can be used with the generic class then you cannot use value type to create an object of generic type.

Constraints can be applied using the where keyword. In the following example, MyGenericClass specifies the constraints that only a reference type can be used with MyGenericClass. This means that only a class can be a placeholder type not the primitive types, struct etc.

Example: Generic class with constraints

class MyGenericClass<T> where T: class
    {
        private T genericMemberVariable;

        public MyGenericClass(T value)
        {
            genericMemberVariable = value;
        }

        public T genericMethod(T genericParameter)
        {
            Console.WriteLine("Parameter type: {0}, value: {1}", typeof(T).ToString(),genericParameter);
            Console.WriteLine("Return type: {0}, value: {1}", typeof(T).ToString(), genericMemberVariable);
            
            return genericMemberVariable;
        }

        public T genericProperty { get; set; }
    }

So now, you cannot use int as a placeholder type. The following would give a compile time error.

Example: Compile time error

MyGenericClass<int> intGenericClass = new MyGenericClass<int>(10);

String or any class type is a valid type because it is a reference type.

Generic class:

MyGenericClass<string> strGenericClass = new MyGenericClass<string>("Hello World");

MyGenericClass<Student> strGenericClass = new MyGenericClass<Student>(new Student());

The following table lists the types of generic constraints.

Constraint Description
where T : class Type must be reference type.
where T: struct Type must be value type.
where T: new() Type must have public parameterless constructor.
where T: <base class name> Type must be or derive from the specified base class
where T: <interface name> Type must be or implement the specified interface.
where T: U Type supplied for T must be or derive from the argument supplied for U.

Multiple constraints:

A generic class can have multiple constraints as shown below.

Multiple constraints:

class MyGenericClass<T, U> where T: class where U:struct
{
    ...
}

Constraint on generic methods:

You can apply constraints on the generic methods also.

Method constraint:

class MyGenericClass<T> where T: class 
{
    public T genericMethod<U>(T genericParameter, U anotherGenericType) where U: struct
    {
        Console.WriteLine("Generic Parameter of type {0}, value {1}", typeof(T).ToString(),genericParameter);
        Console.WriteLine("Return value of type {0}, value {1}", typeof(T).ToString(), genericMemberVariable);
            
        return genericMemberVariable;
    }        
}

Thus, constraints can be applied on generic types.

Points to Remember :

  1. Constraints specifies the kind of types allowed with the generics.
  2. Constraints can be applied using the where keyword.
  3. Six types of constraints can be applied: class, struct, new(), base class name, interface and derived type.
  4. Multiple constraints also can be applied.