24 out of 36 rated this helpful - Rate this topic

Constraints on Type Parameters (C# Programming Guide)

Updated: July 2008

When you define a generic class, you can apply restrictions to the kinds of types that client code can use for type arguments when it instantiates your class. If client code tries to instantiate your class by using a type that is not allowed by a constraint, the result is a compile-time error. These restrictions are called constraints. Constraints are specified by using the where contextual keyword. The following table lists the six types of constraints:

Constraint

Description

where T: struct

The type argument must be a value type. Any value type except Nullable can be specified. See Using Nullable Types (C# Programming Guide) for more information.

where T : class

The type argument must be a reference type; this applies also to any class, interface, delegate, or array type.

where T : new()

The type argument must have a public parameterless constructor. When used together with other constraints, the new() constraint must be specified last.

where T : <base class name>

The type argument must be or derive from the specified base class.

where T : <interface name>

The type argument must be or implement the specified interface. Multiple interface constraints can be specified. The constraining interface can also be generic.

where T : U

The type argument supplied for T must be or derive from the argument supplied for U.

If you want to examine an item in a generic list to determine whether it is valid or to compare it to some other item, the compiler must have some guarantee that the operator or method it has to call will be supported by any type argument that might be specified by client code. This guarantee is obtained by applying one or more constraints to your generic class definition. 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. Constraints are applied by using the contextual keyword where. The following code example demonstrates the functionality we can add to the GenericList<T> class (in Introduction to Generics (C# Programming Guide)) by applying a base class constraint.

public class Employee
{
    private string name;
    private int id;

    public Employee(string s, int i)
    {
        name = s;
        id = i;
    }

    public string Name
    {
        get { return name; }
        set { name = value; }
    }

    public int ID
    {
        get { return id; }
        set { id = value; }
    }
}

public class GenericList<T> where T : Employee
{
    private class Node
    {
        private Node next;
        private T data;

        public Node(T t)
        {
            next = null;
            data = t;
        }

        public Node Next
        {
            get { return next; }
            set { next = value; }
        }

        public T Data
        {
            get { return data; }
            set { data = value; }
        }
    }

    private Node head;

    public GenericList() //constructor
    {
        head = null;
    }

    public void AddHead(T t)
    {
        Node n = new Node(t);
        n.Next = head;
        head = n;
    }

    public IEnumerator<T> GetEnumerator()
    {
        Node current = head;

        while (current != null)
        {
            yield return current.Data;
            current = current.Next;
        }
    }

    public T FindFirstOccurrence(string s)
    {
        Node current = head;
        T t = null;

        while (current != null)
        {
            //The constraint enables access to the Name property.
            if (current.Data.Name == s)
            {
                t = current.Data;
                break;
            }
            else
            {
                current = current.Next;
            }
        }
        return t;
    }
}


The constraint enables the generic class to use the Employee.Name property because all items of type T are guaranteed to be either an Employee object or an object that inherits from Employee.

Multiple constraints can be applied to the same type parameter, and the constraints themselves can be generic types, as follows:

class EmployeeList<T> where T : Employee, IEmployee, System.IComparable<T>, new()
{
    // ...
}


By constraining the type parameter, you increase the number of allowable operations and method calls to those supported by the constraining type and all types in its inheritance hierarchy. Therefore, when you design generic classes or methods, if you will be performing any operation on the generic members beyond simple assignment or calling any methods not supported by System.Object, you will have to apply constraints to the type parameter.

When applying the where T : class constraint, avoid the == and != operators on the type parameter because these operators will test for reference identity only, not for value equality. This is the case even if these operators are overloaded in a type that is used as an argument. The following code illustrates this point; the output is false even though the String class overloads the == operator.

public static void OpTest<T>(T s, T t) where T : class
{
    System.Console.WriteLine(s == t);
}
static void Main()
{
    string s1 = "target";
    System.Text.StringBuilder sb = new System.Text.StringBuilder("target");
    string s2 = sb.ToString();
    OpTest<string>(s1, s2);
}


The reason for this behavior is that, at compile time, the compiler only knows that T is a reference type, and therefore must use the default operators that are valid for all reference types. If you must test for value equality, the recommended way is to also apply the where T : IComparable<T> constraint and implement that interface in any class that will be used to construct the generic class.

You can apply constraints to multiple parameters, and multiple constraints to a single parameter, as shown in the following example:

class Base { }
class Test<T, U>
    where U : struct
    where T : Base, new() { }


Type parameters that have no constraints, such as T in public class SampleClass<T>{}, are called unbounded type parameters. Unbounded type parameters have the following rules:

  • The != and == operators cannot be used because there is no guarantee that the concrete type argument will support these operators.

  • They can be converted to and from System.Object or explicitly converted to any interface type.

  • You can compare to null. If an unbounded parameter is compared to null, the comparison will always return false if the type argument is a value type.

The use of a generic type parameter as a constraint is useful when a member function with its own type parameter has to constrain that parameter to the type parameter of the containing type, as shown in the following example:

class List<T>
{
    void Add<U>(List<U> items) where U : T {/*...*/}
}


In the previous example, T is a type constraint in the context of the Add method, and an unbounded type parameter in the context of the List class.

Type parameters can also be used as constraints in generic class definitions. Note that the type parameter must be declared within the angle brackets together with any other type parameters:

//Type parameter V is used as a type constraint.
public class SampleClass<T, U, V> where T : V { }


The usefulness of type parameters as constraints with generic classes is very limited because the compiler can assume nothing about the type parameter except that it derives from System.Object. Use type parameters as constraints on generic classes in scenarios in which you want to enforce an inheritance relationship between two type parameters.

Date

History

Reason

July 2008

Added paragraph on multiple constraints.

Content bug fix.

Did you find this helpful?
(1500 characters remaining)
Community Content Add
Annotations FAQ
Unexpected behavior from constrained generics involving the this keyword

I'm not sure why this doesn't work or even if it should... but it'd be cool if someone could explain it to me!

class foo<T> where T : foo<T>
{
  void bar (T one, T two) {}
  void grog (T three)
  {
    bar(three, this);
  }
}

for some reason it won't accept "this" as the appropriate type...
The inability to express an enum as a type constraint is a bug
In the MSDN documentation on Generic Constraints:

Reference/Value Type Constraint
You can constrain a generic type parameter to be a value type (such as an int, a bool, and enum) or any custom structure using the struct constraint: public class MyClass<T> where T : struct

{...}

Similarly, you can constrain a generic type parameter to be a reference type (a class) using the class constraint: public class MyClass<T> where T : class

{...}


curiously, there is an example of everything but the use of an enum as a constraint.. C# compiler flags "enum" as expecting a type.

Addition by TWiStErRob: the above sentence is saying only that "enum IS A value type"... For using Enum as constraint, see the the first post's 2nd point.


CSTeam edit: I think this is answered by TWiStErRob, here, and Damon, below. Thanks.
This is one of those things to commit to memory

A lack of Generic Constraints often means one of two far nastier occurrences:

  • No type checking is being done at all
  • Type checking is done using old-school .NET 1.1 Reflection at run-time only which is far better than the above
Now of course there are many cases where you cannot express something in Generic Constraint 'exactly' and you can at run time. Here is our team standard:

  1. Having no generic type checking is flagged as a compile error and will even cause a Continuous Integration Server build to fail.
  2. At least express 'what you can' in the generic type language. For example, you cannot use 'Enum' as a type constraint but you can use 'Struct'. Do as much as possible is the idea.
  3. When necessary try to augment the cases in (c) then add run-time checks at the level you need. Most teams (such as ours as well) have a core framework 'DSL' for Reflection and run-time type checks, as well as the far more complex cases in Reflection where there is simply no managed API.
    1. We'll likely be posting this on the site below not because we think it is incredibly innovative (for some we hope) but instead because there are terribly 'necessary' tactical framework code that becomes far more valuable now with Extension Methods and Linq. Typically we extend the Type 'Type'.

Check the address below for a full and extended use case code sample with full regression / unit test coverage (to make it painless to understand and so you don't have to dig into the code to see the value).


This is not about "you're not going to need it" Agile thought of which we believe by common sense, this is where something is considered damaging to the overall code base integrity. This is something to not accept, much like the old 'variant' type was not really viable, and that is what this would amount to : A type that becomes anything (well as long as you didn't have type inference happening for you! Ah.. I love C# 3.0)...


Kind Regards,
Damon Wilder Carr
CTO, agilefactor
Team Lead, domain.dot.net project
http://blog.domaindotnet.com
Advertisement