DEV Community

Dane Balia
Dane Balia

Posted on • Updated on


Quick Tour of C# Generic Type Constraints

Last time we looked at some fundamentals to Generics. Let's peel a little more of the onion regarding Generics and consider Type Constraints.

A Type Constraint is the restriction imposed on a type used by a generic.

public class AGenericClass<T> where T : IComparable<T> { } ...
public class UsingEnum<T> where T : System.Enum { } ...
public class GenericList<T> where T : Employee ...

Constraints inform the compiler about the capabilities a type argument must have by restricting it. Without any constraints, the type argument could be any type. Imposing the constraint/s on the generic increases the number of allowable operations and method calls to those supported by the constraining type and all types in its inheritance hierarchy.


So if I had a wooden drawer, it could pretty much take anything. In fact, it would most likely be very difficult to order things and keep things in their place. And with the continual opening and closing of the drawer, one is also unlikely to efficiently access their papers, pens, rulers, stapler etc.
But assume we have a drawer compartmentalized by inner linings of wood. All of a sudden, things have a place and are more structured in terms of access. And this is what Type constraints do. They impose restrictions to provide a more human and safe interface to the Generic type - <T>.

Seven Constraints

There are 7 constraints one could consider when using a Generic, so let's review a few to get an idea of how to use them.

Value Type Constraint

where T : struct

This constrains the type argument to a value and not a reference type.
Examples of what would be valid:

  • Votes<int>
  • UploadedFiles<FileMode>

Examples of what would be invalid:

  • Swap<object>
  • ReportContent<StringBuilder>

Reference Type Constraint

where T : class

This constrains the type argument to be a reference type and not a value type.
Examples are simply the reverse of what we just saw in the Value Type Constraint. That is, the type argument <T> can be any class, interface, array or delegate.

Constructor Type Constraint

where T : new()

This constrains the type argument to have a public parameter-less constructor.

snip taken from C# in Depth

public T CreateInstance<T>() where T : new()
   return new T();

This methods returns a new instance of whatever type you specify, constrained of course by the fact that the type must have no parameters in its constructor. This is a useful constraint when working with the Factory Method Design Pattern.

Sample Using Interface Constraint

In the sample above, we see the creation of a rudimentary Tuple. The type MyTuple has an interface type constraint. Thus the type argument must be or implement IEquatable .

When developing our MyTyple; IEquatable enforces the following constraints:

  1. You must implement it's method - Equals(<T>)
  2. And you should override the base class implementations of Equals(Object) and GetHashCode() so that their behaviour is consistent with that of the Equals(<T>) method.

There are four main generic interfaces for comparisons, two of them IComparer<T> and IComparable<T> are about comparing values and for ordering. That is, one value <=, => or == too another.

The other two, IEqualityCompare<T> and IEquatable<T> are used for comparing two items for equality to some criteria and applying a hash of an item.

The consumer of the code is constrained by a contract that describes the bounds of the generic. So while the generic is supportive of variations of a kind (or type) it is prescriptive around what it should do or be capable off.

Last words

Generic type constraints are nothing more then the restrictions imposed on generic types preventing abuse, but also clearly communicating the constraints for its use and providing compile-time errors.

For more depth, please see the resource below:

  1. Introduction to IEquatable
  2. You must implement Equal and HashCode
  3. IEquality Interface
  4. Why use constraints?

Top comments (0)