Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Constraints in Generics

0.00/5 (No votes)
20 Dec 2006 1  
This article discusses the problems with Generics and how to tackle them with out of the box solutions provided with .NET Framework 2.0

Introduction

Generics, no doubt, is one of the wonderful and refreshing features introduced in Microsoft .NET Framework 2.0. Generics can be used to produce strictly type safe data structures, thus providing enormous performance optimization. Introduction of generics brought a near end to the use of boxing/unboxing to store types in data structures.

This article assumes knowledge of C# 2.0.

Syntax

The syntax of using generics is:

//usage with class 
class MyClass< T >
{
  ...
}

//usage with method
void MyMethod< U >()
{
  ...
}

Problems with Generics

Let us consider the following example:

//this method returns if both the parameters are equal 
public static bool Equals< T > (T t1, Tt2) 
{ 
  return (t1 == t2); 
}

Apparently, this code is a very good usage of generics. This method can be used to compare the equality of any two similar types. The problem here is that, the IL generated to compare say an integer and a string is different. int comparison is just check the values on the stack, whereas it is not as simple for string. So the IL generated for Equals<string> would be different to that of Equals<int>.

The case may be even different if the types being compared have a new definition of == operator. Because of this, you may not use the == operator to compare any generic object references without having any type of restrictions/constraints on them.

Solution

There are two possible solutions for this (that were bundled out of the box) with C#:

  1. Runtime casting
  2. Restricting the allowable types while declaring the generic type

Runtime casting (a.k.a. yuck!), sometimes, can be a good fit here. In this, the CLR will cast the types at the runtime dynamically thus ensuring the similar functional behavior throughout the application. But, this certainly is not the best way always, especially not when the types being used are overriding the default behavior of the operators (just an example) involved in the operation.

The best fit, for most of the cases would certainly be having some kind of restriction on what types should be allowed to be replaced in the generic type. In .NET, they are called constraints.

Constraints are represented in C# using the where keyword. The following is the syntax:

public bool Compare< T > (T t1, Tt2)
       where T : IComparable 
{
  ...
} 

Some of the ways we can use constraints are as follows:

  1. Specifying the type to be a reference type:

    public void MyMethod< T >()
           where T : class
    {
      ...
    } 

    Please note that class is the keyword here and should be used in the same case. Any difference in the case will lead to a compilation error.

  2. Specifying the type to be a value type:

    public void MyMethod< T >()
           where T : struct
    {
      ...
    } 

    Please note that struct is the keyword here and should be used in the same case. Any difference in the case will lead to a compilation error.

  3. Specifying a constructor as a constraint:

    public void MyMethod< T >()
            where T : new ()
    {
      ...
    } 

    Please note that  only a default constructor can be used in the constraints and using any parameterised constructor will be a compilation error.

  4. Specifying a static base class as a constraint:

    public void MyMethod< T >()
           where T : BaseClass
    {
      ...
    }
  5. Specifying a generic base class as a constraint:

    public void MyMethod< T, U >()
           where T : U
    {
      ...
    } 

Points to Ponder

Though this list seems to be insufficient considering the complex scenarios, these can be mixed appropriately.

The below is a valid combination of constraints:

public void MyMethod< T >()
       where T : IComparable, MyBaseClass, new ()
{
  ...
}

//Here we are using multiple constraints. 
//using IComparable and MyBaseClass would be case 4, using new() would be case 3.

The below is an example for an invalid combination of constraints:

public void MyMethod< T >()
       where T : class, struct
{
  ...
}

//here we are trying to specify the 
//generic type as both reference and value type.

Summary

Generics are a wonderful feature to work with and should be used wherever appropriate for better optimization of the applications.

Please get back to me for any additional information/suggestions/clarifications/shortcomings regarding this topic.

History

  • 20th December, 2006: Initial post

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here