Telerik blogs
TB_870x220

When working with generic classes or methods, it can be useful to constrain the types that can be used with them. There are many constraints we can apply. Learn what they are and how to use them.

Generics make it possible to design classes and methods that defer the specification of one or more types until the class or method is declared and instantiated by client code. When the compiler encounters a constructor for the class or a function call for the method, it generates code to handle the specific data type. Generic classes can be constrained to be used only on certain data types. This increases the number of allowable operations and method calls to those supported by the constraining type and all types in its inheritance hierarchy. I’ll introduce you to the various ways you can constrain generic types in C#.

Constrain by Value Type

You can constrain a generic type to a value type by setting the constraint of the type as follows.

class ConstrainByValueType<T> where T : struct { }

Here the struct keyword is used to constrain T to a value type. The object can then be instantiated like new ConstrainByValueType<double>, and you can specify any value type as you want. Be aware that you can’t use a nullable value type, so this will fail, new ConstrainByValueType<double?>.

Constraint to Allow Only Reference Types

You can also constrain the type to allow only reference types. Similar to how you would do it for value types, you would use the class keyword to constrain the type to a reference type.

class ConstrainByReferenceType<T> where T : class { }

Interface Type Constraint

You can constrain the generic type by interface, thereby allowing only classes that implement that interface or classes that inherit from classes that implement the interface as the type parameter. The code below constrains a class to an interface.

interface IAnimal { }
class Snake : IAnimal { }
interface IMammal : IAnimal { }
class Lion : IMammal { }
class CuteLion : Lion { }

class ConstrainByInterface<T> where T : IMammal { }

The type T above is constrained to the IMammal interface, which allows only classes that implements this interface (or classes that inherit from a class that implements the interface) to access the generic type. Even if the IMammal inherits from the IAnimal interface, you can’t use Snake as the type for T. You can only use Lion or CuteLion type.

Constrain by Class

We can restrict a generic type to only allow type parameters of a specific class, or classes that inherit from that specific base class. Following the example of the previous section, let’s create a generic class which is restricted to the Lion class.

class ConstrainByClass<T> where T : Lion { }

In this scenario, only the Lion class or a class that inherits from Lion can be used to instantiate this generic type.

Another constraint we can apply is to ensure that the class that’ll be used as the type parameter has a public, parameterless constructor. We do this by using the keyword new() as the constraint for the generic type.

class ConstrainByParameterlessCtor<T> where T : new() { }

With the constraint above, the ConstrainByParameterlessCtor class is restricted to use classes which have a public parameterless constructor.

Using Enum as Constraint

Beginning in C# 7.3, you can specify the System.Enum type as a constraint. The code below is an example of how to use enums as constraint for generics.

class Program
{
  class ConstrainByEnum<T> where T : System.Enum
  {
    public void PrintValues()
    {
      var values = Enum.GetValues(typeof(T));
      foreach (int item in values)
        Console.WriteLine(Enum.GetName(typeof(T), item));
    }
  }

  enum Rainbow
  {
    Red,
    Orange,
    Yellow,
    Green,
    Blue,
    Indigo,
    Violet
  }

  static void Main(string[] args)
  {
    var enumGeneric = new ConstrainByEnum<Rainbow>();
    enumGeneric.PrintValues();
    Console.Read();
  }
}

The type T as you see is restricted to enums, and the class has a PrintValues method prints the enum type values to the console. Running the code should print out the following:

Red
Orange
Yellow
Green
Blue
Indigo
Violet

Microsoft Documentation also shows an example that used to make use of reflection, but with this feature allowed, it no longer uses reflection and has improved performance.

Beginning with C# 7.3, we can use the unmanaged constraint to specify that the type parameter must be an unmanaged type, and System.Delegate or System.MulticastDelegate as a base class constraint. The documentation provide an example and details on using the unmanaged constraint and delegate constraint.

Combining Constraints

You’ve seen how we can apply a single constraint to generic types. While that is valuable, we can also use these constraints in combination.

class Program
{
  interface IMammal { }

  class CuteLion : IMammal
  {
    private CuteLion() { }
  }

  class RainbowLion : IMammal { }

  class ConstrainByCombination<T> where T : IMammal, new() { }

  static void Main(string[] args)
  {
    new ConstrainByCombination<RainbowLion>(); // Valid
    new ConstrainByCombination<CuteLion>(); // Invalid
    Console.Read();
  }
}

In the example you see above, we’re constraining T to use IMammal interface and must have a public parameterless constructor. We have two classes which implement the IMammal interface. The CuteLion class implements this interface but has a private parameterless constructor, and this doesn’t satisfy the condition to use it the type for the generic class. The RainbowLion class satisfies this condition, therefore, can be used as the type parameter for the generic class.

Conclusion

When working with generic classes or methods, it’s sometimes useful to constrain the types that can be used with them. There’s a number of constraints that we can apply and this article sums up those constraints and how we can use them. We also looked at new constraints that are allowed starting from C# 7.3 and how to combine multiple constraints to enforce stronger rules on generics.


Peter Mbanugo
About the Author

Peter Mbanugo

Peter is a software consultant, technical trainer and OSS contributor/maintainer with excellent interpersonal and motivational abilities to develop collaborative relationships among high-functioning teams. He focuses on cloud-native architectures, serverless, continuous deployment/delivery, and developer experience. You can follow him on Twitter.

Comments

Comments are disabled in preview mode.