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:
class MyClass< T >
{
...
}
void MyMethod< U >()
{
...
}
Problems with Generics
Let us consider the following example:
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#:
- Runtime casting
- 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:
-
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.
-
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.
-
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.
-
Specifying a static
base class as a constraint:
public void MyMethod< T >()
where T : BaseClass
{
...
}
-
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 ()
{
...
}
The below is an example for an invalid combination of constraints:
public void MyMethod< T >()
where T : class, struct
{
...
}
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