C# Generics

Summary

Introduction

Generics is a new feature in C#  2.0. Generics allow you to define type-safe data structures without specifying an actual data type. Generics are similar to C++ templates in concept, but are very different in implementation and capabilities.

To see what problems Generics solve, consider a very simple data structure such as the stack, providing the basic Push() and Pop() operations. Typically, you would like each stack instance to store instances of various types. For example, you may need a stack of ints, a stack of strings and so on. In C# 1.0 you have to use an object-based stack, meaning that the internal data type used in the stack is an instance of object and methods must interact with object instances:

// Sample implementation
public class Stack
{
    object[] m_aItems = null;

    public void Push( object item )
    { ... }

    public object Pop()
    { ... }
}

// Sample Usage
Stack s = new Stack();
s.Push( 1 );
s.Push( 2 );
int n = (int)s.Pop();

There are two main problems in this approach:

  1. Performance. When using value types, they have to be boxed in order to push and store them as objects (a reference type), and they have to be unboxed when popping them off the stack. Boxing and unboxing incur a significant overhead as boxing allocates a new object on the heap which increases the pressure on the managed heap resulting in more garbage collections, which obviously affects performance. Even when using reference types, there is still a performance hit as they have to be casted up to an object when pushing and down to the original type when popping.
     

  2. Type-safety. This is a more severe problem because the compiler allows you to cast any type to and from object. You therefore lose compile-time type-safety. For example, the following code compile fine (when it should not) and raises an InvalidCastException at runtime:

Stack stack = new Stack();
stack.Push( 1 );
string s = (string)stack.Pop();    // InvalidCastException. We should cast to an int.

In C# 1.0, you can overcome these two problems by providing a type-specific (and hence type-safe) stack. For example, you would have to implement an IntStack for integers and StringStack for strings:

// Sample implementation
public class IntStack
{
    int[] m_aItems = null;

    public void Push( int item )
    { ... }

    public int Pop()
    { ... }
}

// Sample implementation
public class StringStack
{
    string[] m_aItems = null;

    public void Push( string item )
    { ... }

    public string Pop()
    { ... }
}

And so on. Obviously, while this approach solves the performance and type-safety problems, it does introduce a third and more serious problem - productivity impact. Writing type-safe data structures is a tedious, repetitive and error-prone task. Enter Generics.

What Are Generics

Generics allows you to define type-safe classes without compromising type-safety, performance, or productivity. The main approach (just as in C++), you implement a generic data structure only once, and then instantiate it with a specific data type. Classes, interfaces, structures, delegates, instance and static methods can all be generic types. Here is how you define and use a generic Stack:

public class Stack<T>
{
    T[] m_aItems = null;

    public void Push(T item)
    { /* ... */ }

    public T Pop()
    { /* ... */ }
}

Stack<int> stack = new Stack<int>();     // Note syntax to tell the compiler which type to use instead of the generic type parameter T
stack.Push( 10 );                             // VS.NET 2005 Intellisense shows that Push() expects an int parameter
stack.Push( 20 );
int n1 = stack.Pop();                         // VS.NET 2005 Intellisense shows that Pop() returns an int
int n2 = stack.Pop();.

Generics have native support in IL and the CLR. When you compile generics C# code, the compiler converts it to IL just like any other compiled code, however, the IL will contain placeholders for the actual specific types. In addition, the metadata for the generic code contains generic information. When a program provides a specific type instead of the generic type parameter T, the specific type will replace the generic type parameter T as if generics were never involved.

Generics should be used whenever a data structure or a utility class can be expressed in a generic version, and not the object-based version. Typically, collections and data structures such as linked lists, queues, binary trees etc will offer generic support, but generics are not limited to data structures. Often, utility classes such as class factories or formatter can take advantage of generics. However, note that generics are not necessarily faster that non-generic code. If the non-generic code is using instances of object as the containers, then generics are definitely faster, but if the non-generic code is using type-specific data structures, then there is possibly no performance benefit to generics (problems relating to type-safety and productivity still apply though).

 

Applying Generics

Note on naming Generics conventions which are used throughout:

Because of the native support for generics in IL and the CLR, most CLR-compliant languages can take advantage of generic types. The following simple examples show how to apply generics:

// Example 1
internal class MyPoint<T>
{
    private T m_x;
    private T m_y;

    public MyPoint()
    {
        m_x = default(T);    // default keyword is specific to generics and returns the default value of the specified type
        m_y = default(T);    // default keyword is specific to generics and returns the default value of the specified type
    }

    public MyPoint(T x, T y)
    {
        m_x = x;
        m_y = y;
    }

    public T X
    {
        get { return m_x; }
        set { m_x = value; }
    }

    public T Y
    {
        get { return m_y; }
        set { m_y = value; }
    }
}

MyPoint<int> intPt = new MyPoint<int>();
intPt.X = 10;
intPt.Y = 20;
Trace.WriteLine(intPt.X + "," + intPt.Y);

MyPoint<double> dblPt = new MyPoint<double>();
dblPt.X = 10.0;
dblPt.Y = 20.0;
Trace.WriteLine(dblPt.X + "," + dblPt.Y);

Note: the default keyword returns the default value of the type argument. For value types, the default value corresponds to filling the struct (all value types are structs) with zeros. For reference types, the default value is null.

// Example 2
// This class represents a node whose types are generic

internal class Node<K, I>
{
    public K             key;
    public I             item;
    public Node<K, I>    nextNode;

    public Node()
    {
        key      = default(K);        // Note use of default
        item     = default(I);        // Note use of default
        nextNode = null;
    }

    public Node(K k, I i, Node<K, I> next)
    {
        key      = k;
        item     = i;
        nextNode = next;
    }
}

// This class represents a linked list whose node types are generic. The fact the LinkedList uses the same names
// as the node for the generic type arguments is purely for readability purposes. Other names for K and I could
// have been used.
internal class LinkedList<K, I>
{
    internal Node<K, I> m_head;

    public LinkedList()
    {
        // We must provide type arguments when instantiating a generic type. Note how we use the linked list's
        // own generic type arguments
        m_head = new Node<K, I>();
    }

    public void AddHead(K k, I i)
    {
 
        // Again, we must provide type arguments when instantiating a generic type. Note how we use the linked list's
        // own generic type arguments
 
        Node<K, I> node = new Node<K, I>(k, i, m_head.nextNode);
        m_head.nextNode = node;
    }
}

LinkedList<int, string> list = new LinkedList<int, string>();
list.AddHead(10, "Ten");
list.AddHead(20, "Twenty");

LinkedList<double, DateTime> list2 = new LinkedList<double, DateTime>();
list2.AddHead(10.0, DateTime.Now.AddDays(1));
list2.AddHead(20.0, DateTime.Now.AddDays(2));

using List = LinkedList<int, string>;    // Providing an alias for a particular combination of types.
                                         // The scope of aliasing is the scope of the file

List list = new List();
list.AddHead( 10, "hello" );

Note the following naming conventions for generic types (assume the generic type is LinkedList<K,I>):

Generic Constraints

Generic constraints solve the following problem: Generic code can contain code that is incompatible with the specified type argument. Generic constraints imposes certain constraints or limitations on the type arguments in order to ensure that the generic code behaves correctly. For example, a generic function inside a generic class may need to call ICloneable.Clone on the type argument, meaning that the generic function assumes that the type argument implements ICloneable. If the supplied type argument did not implement ICloneable, a runtime exception will be generated and the code fails. Generic constraints solve these kinds of problems.

In fact, there are three types of generic constraints:

  1. Derivation Constraint: This indicates to the complier that the generic type parameter derives from a base type such as an interface or a base class.

  2. Default Constructor Constraint: This indicates to the complier that the generic type parameter has a default constructor (a public constructor with no arguments.)

  3. Reference/value type Constraint: This indicates to the complier that the generic type parameter is a reference or a value type.

It is important to note that while constraints are optional, they can be essential when developing a generic type. Without them, the compiler takes a more conservative and type-safe approach and only allows access to object-level functionality in your generic type parameters. Also note that constraints are part of the generic type metadata so that the compiler can take advantage of them. The compiler reads the constraints from the metadata and only allows type arguments that comply with the constraints, thus enforcing type safety.

Derivation Constraint

This constraint uses the where keyword to indicate to the compiler that a generic type parameter derives from some base type. The compiler will generate an error (CS0309) if a type argument did not derive from the given base class. The following examples illustrate:

Example 1: Basic derivation constraint.

public interface I
{
    /* ... */
}

public class A : I
{
    /* ... */
}

public class B
{
    /* ... */
}

public class C<T> where T : I    // Generic type parameter T must derive from interface I
{
    /* ... */
}

C<A> c1 = new C<A>();    // A derives from interface  I, hence code compiles.
C<B> c2 = new C<B>();    // B does not derive from interface I, hence compiler error CS0309

Example 2: All derivation constraints must appear after the actual derivation list of the generic class.

public class C<T> : IEnumerable<T> where T : I    // C<T> derives from IEnumerable<T>. Also generic type argument T must derive from interface I
{
    /* ... */
}

Example 3: You can provide a derivation constraint for each generic type argument.

public class A<T, K> where T : MyBase        // A base class in a derivation constraint list cannot be sealed or
                                             // static. And there can only one base class.

                     where K : IComparable<K>
{ /* ... */ }

Example 4: You can constrain a base class and/or multiple interfaces on the same generic type argument. If a base class is specified, it must appear first in the derivation constraint list.

public class A<T,K> where T: MyBase, IEnumerable, ICloneable<T>
{ /* ... */ }

Example 5: When dealing with a derivation constraint, the type argument can specify the base type itself, and not necessarily a derived class.

public interface IMyInterface
{ /* ... */ }

public class MyClass : IMyInterface
{ /* ... */ }

public class A<T> where T : IMyInterface
{ /* ... */ }

A<IMyInterface> a = new A<IMyInterface>();

Default Constructor Constraint

This constraint uses the new keyword to indicate to the compiler that a generic type parameter supports a public default constructor (a constructor with no parameters). For example, suppose you want to instantiate inside the generic class a new object from the type argument. The compiler will generate error CS0304 if the type argument did not support a default constructor. The following example illustrates:

// Does not compile
public class D<T>
{
    public T m_nID = new T();   // error CS0304: Cannot create an instance of the variable type 'T' because it does not have the new() constraint

    // ...
}

// Same example as above, but uses the new constraint
public class D<T> where T : new()
{
    public T m_nID = new T();    // OK
    // ...
}

// Same example as above but combining the default constructor constraint with a derivation constraint (default
// constructor constraint must appear last)

public class D<T> where T : SomeBase, new()
{
    /* ... */
}

Reference/Value type Constraint

This constraint uses the struct/class keywords to indicate to the compiler that a generic type parameter is a value type/reference type, respectively. The following example illustrates:

public class A<T> where T : struct        // T must be a value type
{ /* ... */ }

public class B<T> where T : class        // T must be a reference type
{ /* ... */ }

A<int> a1    = new A<int>();         // OK. Type argument is value-type
B<string> b1 = new B<string>();     // OK. Type argument is reference-type
A<string> a2 = new A<string>();     // error CS0453: The type 'string' must be a non-nullable value type in order
                                     // to use it as parameter 'T' in the generic type or method 'Generics.A<T>'

Note that reference/value constraint cannot be used with a base class constraint, but can be combined with any other constraint. When used, a reference/value constraint must appear first.

Generics and Casting

Note the following points:

internal interface IMyInterface
{
    void foo();
}

internal class MyBaseClass
{
    public void bar() { /* ... */ }
}

internal class MyClass<T> where T : MyBaseClass, IMyInterface
{
    public void SomeMethbod(T t)
    {
        // Can implicitly cast to a constraint-specified interface
       
IMyInterface itf = t;

        // Can implicitly cast to a constraint-specified class
       
MyBaseClass c = t;

        // Can implicitly cast to object
       
object o = t;
    }
}

internal interface IMyInterface
{
    void foo();
}

internal class MyBaseClass
{
    public void bar() { /* ... */ }
}

internal class MyClass<T>            // Note this class has no derivation-constraints
{
    public void SomeMethod(T t)
    {
        IMyInterface itf = t;        // error CS0266: Cannot implicitly convert type 'T' to 'Generics.Casting2.IMyInterface'.
                                     // An explicit conversion exists (are you missing a cast?)
        MyBaseClass mbc = t;         // error CS0029: Cannot implicitly convert type 'T' to 'Generics.Casting2.MyBaseClass'

        IMyInterface itf2 = (IMyInterface)t;             // OK
        MyBaseClass mbc2  = ((MyBaseClass)(object)t);    // OK
    }
}

internal interface IMyInterface
{
    void foo();
}

internal class MyBaseClass
{
    public void bar() { /* ... */ }
}

internal class MyClass<T>
{
    public void SomeMethod(T t)
    {
        // Using 'is'
        IMyInterface itf = null;
        if ( t is IMyInterface)
            itf = (IMyInterface)t;

        // Using 'as'
        MyBaseClass mbc = t as MyBaseClass;
        if (mbc != null)
            mbc.bar();
    }
}

Generics and Inheritance

Note the following points:

internal class MyGenericBaseClass<T>             { /* ... */ }
internal class MyClass : MyGenericBaseClass<int> { /* ... */ }

internal class MyGenericBaseClass<T>                         { /* ... */ }
internal class MyGenericSubClass<T> : MyGenericBaseClass<T>  { /* ... */ }

internal class MyGenericBaseClass2<T> where T : ICloneable                         { /* ... */ }
internal class MyGenericSubClass2<T> : MyGenericBaseClass2<T> where T : ICloneable { /* ... */ }

 

internal class MyGenericBaseClass3<T>
{
    public virtual void SomeMethod(T t) { /* ... */ }
}

internal class MyClass2 : MyGenericBaseClass3<int>
{
    public override void SomeMethod(int n)
    {
        { /* ... */}
    }
}

internal interface IMyInterface<T>
{
    T SomeMethod(T t);
}

internal abstract class MyAbstractGenericClass<T>
{
    public abstract void SomeMethod(T t);
}

internal class MyDerivedGenericClass<T> : MyAbstractGenericClass<T>
{
    public override void SomeMethod(T t) { /* ... */ }
}

public class Calculator<T>
{
    public T Add(T lhs, T rhs)
    {
        return lhs + rhs;         // error CS0019: Operator '+' cannot be applied to operands of type 'T' and 'T'
    }
}

public interface ICalculator<T>
{
    T Add(T lhs, T rhs);
}

public class Calculator2 : ICalculator<int>
{
    public int Add(int lhs, int rhs)
    {
        return lhs + rhs;
    }
}

Generic Methods

Instance Methods

The following example illustrates issues relating to instance generic methods:

internal class MyClass<T>
{
    private T m_t = default(T);

    // No need to specify <T> here. In fact, specifying <T> for Method gives warning CS0693: Type parameter 'T'
    // has the same name as the type parameter from outer type 'MyClass<T>'

    public void Method<T>( T t )
    {
        Trace.WriteLine("t = " + t);
    }


    // This method has a generic type parameter similar to that of the containing class.
    // Note that you cannot provide method-level constraints for class-level generic type
    // parameters
    // public void Method1(T t) where T : new
// Does NOT compile
   
public void Method1(T t)
    {
        Trace.WriteLine("t = " + t);
    }

    // A method can also define its own generic type parameters that are different from
    // those defined at the class level
   
public void Method2<S>(S s, T t)
    {
        Trace.WriteLine("s = " + s + " t = " + t);
    }

    // When a method defines its own generic type parameter, the method can define
    // constraints for these types as well
   
public void Method3<W>(W w) where W : struct
    {
        Trace.WriteLine("w = " + w);
    }

    // The ability to define generic type parameters at the method level cannot be
    // applied to properties and indexers. Properties and indexers can only use generic
    // type paramters defined at the class level. The following does not compile
    // public X ID<X>
    // {
    // get { return default(X); }
    // }
    public T ID
    {
        get { return m_t; }
        set { m_t = value; }
    }
}

// When calling a method that defines its own generic type parameter, you can either provide the
// type as is done in the first call, or you can let the compiler infer the type based on the
// type of the parameter that was passed in (this compiler ability is called 'generic type inference'.
// Both calls below to Method2 compile
MyClass<int> obMyClass = new MyClass<int>();
obMyClass.Method2<string>("hello", 20);        // OK
obMyClass.Method2("inferred", 30);              // OK

// Calling a method that defines constraints on its own generic type parameters
obMyClass.Method3<double>(10.0);

Another example relating to instance generic methods but for subclasses as well:

internal class MyClass2        // Not a generic class!
{
    // You can define generic methods even if the class does not use generics at all
    public virtual void Method1<T>(T t) where T : struct
    {
        Trace.WriteLine(" t = " + t);
    }
}

internal class MySubClass2 : MyClass2
{
    // Sub-class implementation must override the base virtual generic method but should not re-specify
    // any constraints at all, else error CS0460 is generated: 'Constraints for override and explicit
    // interface implementation methods are inherited from the base method, so they cannot be specified
    // directly'
    public override void Method1<T>(T t)         //No constraints can be specified here
    {
        Trace.WriteLine(" t = " + t);

        // If calling the base class implementation, you can either specify the type argument
        // or rely on type inference
        base.Method1<T>(t);         // OK
        base.Method1(t);            // OK
}
}

// When calling a method that defines its own generic type parameter, you can either provide the
// type as is done in the first call, or you can let the compiler infer the type based on the
// type of the parameter that was passed in (this compiler ability is called 'generic type inference'.
// Both calls below to Method2 compile
MyClass2 obMyClass2 = new MyClass2();
obMyClass2.Method1<int>(10);        // OK
obMyClass2.Method1(20);             // OK

In the example above, Method1 defines generic type parameters independently of its containing class. The main benefit is that you can call Method1 each time with a different type argument, without ever overloading the method.

Static Methods

The following example illustrates issues relating to static generic methods:

internal class MyClass<T>
{
    // You can define static methods that use generic type parameters
   
public static T SomeMethod(T t)
    {
        Trace.WriteLine("t = " + t);
        return t;
    }

    // Just like instance methods, static methods can define method-specific generic
    // type parameters and constraints
   
public static void SomeMethod2<X>(X x, T t) where X : class
    {
        Trace.WriteLine("x = " + x + " t = " + t);
    }
}

// Calling a static generic method
int n = Generics.StaticMethods.MyClass<int>.SomeMethod(10);

// Calling a static generic method that defines method-specific generic type parameters.
// Just like instance methods, you can either explicitly specify the type argument or rely
// on type inference
MyClass<double>.SomeMethod2<string>("hello", 12.34);  // OK
MyClass<double>.SomeMethod2("hello", 12.34);          // OK
MyClass<double>.SomeMethod2<int>(12, 12.34);          // error CS0452: The type 'int' must be a reference type in order to use it as
                                                      // parameter 'X' in the generic type or method 'MyClass<T>.SomeMethod2<X>(X, T)'

Generic Delegates

The following example illustrates generic delegates:

 internal class MyClass<T>
{
    // Delegates can use generic type parameters
   
public delegate void Start(T t);

    public void StartMethod(T t)
    {
        Trace.WriteLine("t = " + t);
    }

    // Delegates can define their own generic type parameters
   
public delegate void End<X>(X x, T t);

    public void EndMethod<X>(X x, T t)
    {
        Trace.WriteLine("x = " + x + " t = " + t);
    }
}

// Delegates defined outside the scope of a class can also use generic type parameters
internal delegate void delStart<T>(T t) where T : class;

// Using a generic delegate
MyClass<string> obMyClass = new MyClass<string>();
MyClass<string>.Start dStart1 = new MyClass<string>.Start(obMyClass.StartMethod);
dStart1("Event data");

// The following instantiation of Start delegate gives the following error: error CS0123: No overload
// for 'Method1' matches delegate 'MyClass<int>.Start'. This is because obMyClass was instantiated with
// a 'string' type argument, hence obMyClass.Method1(T t) expects a string argument
MyClass<int>.Start dStart2 = new MyClass<int>.Start(obMyClass.Method1);
dStart2(20);

// In C# 2.0, you can make a direct assignment of a method reference to a delegate variable
// (whether the delegate variable uses generics or not)
MyClass<string>.Start dStart3 = obMyClass.StartMethod;


// Using a generic delegate that defines its own generic type parameters
MyClass<string>.End<double> dEnd1 = new MyClass<string>.End<double>(obMyClass.EndMethod<double>);
dEnd1(10, "hello");

// Using a delegate define outside the scope of a class. Note that the type argument must
// be supplied for the delegate when declaring and instantiating it

delStart<string> dStart4 = new delStart<string>(obMyClass.StartMethod);
dStart4("hello");

Generic delegates are especially useful when it comes to events. You can literally define a limited set of generic delegates, distinguished only by the number of the generic type parameters they require, and use these delegates for all of your event handling needs. The following code illustrates the use of a generic delegate and a generic event-handling method:

 // Generic event handling
internal delegate void GenericEventHandler<S, A>(S sender, A args) where A : System.EventArgs;    // Note constraints

internal class Publisher
{
    public event GenericEventHandler<Publisher, System.EventArgs> MyEvent;
    public void FireEvent()
    {
        MyEvent( this, new EventArgs());
    }
}

internal class Subscriber
{
    public void EventHandler(Publisher sender, EventArgs args)
    {
        Trace.WriteLine("Received event");
    }
}

// Generic event handling
Publisher pub = new Publisher();
Subscriber sub = new Subscriber();
pub.MyEvent += new GenericEventHandler<Publisher,EventArgs>(sub.EventHandler);
pub.FireEvent();

Generics and Reflection

.NET now supports generic type parameters (recall in Stack<T>, T is the generic type parameter, Stack<T> is the generic type and int in Stack<int> is the type argument). Most of generics support in reflection resolves around the Type class. In the following examples, any generic type with a specified type argument is called a bounded type, and a generic type with an unspecified type argument is called an unbounded type. For example, Stack<int> is a bounded type and Stack<> is an unbounded type.

GetType() method and typeof() operator now support generic types as follows:

public void TestType(T t)
{
    // typeof() and GetType() can operate on generic type parameters
    Type tp1 = t.GetType();                               // Note the use of 't' and not 'T'
    Type tp2 = typeof(T);                                 // Note the use of 'T' and not 't'
    Debug.Assert(tp1 == tp2);
    Trace.WriteLine("tp1 full name = " + tp1.FullName +
                   " tp2 full name = " + tp2.FullName);   // Output: tp1 full name = System.Int32 tp2 full name = System.Int32

    // typeof() and GetType() can operate on bounded generic types
    MyPoint<T> pt = new MyPoint<T>();
    Type tp3 = pt.GetType();                             // Note use of pt, an instance of a bounded generic type
    Type tp4 = typeof( MyPoint<T> );                    // Note use of MyPoint<T>
    Debug.Assert(tp3 == tp4);
    Trace.WriteLine("tp3 full name = " + tp3.FullName +
                    " tp4 full name = " + tp4.FullName);  // Output: tp3 full name = Generics.MyPoint`1[[System.Int32, mscorlib,
                                                          // Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]
                                                          // tp4 full name = Generics.MyPoint`1[[System.Int32, mscorlib, Version=2.0.0.0,
                                                          // Culture=neutral, PublicKeyToken=b77a5c561934e089]]


    // typeof() can operator on unbounded generic types. GetType() cannot be used because
    // GetType() operates on an instance and it is a compile-time error to get an instance of
    // an unbounded generic type (i.e., instantiating MyPoint<> gives a compile time error)

    Type tpUnbounded1 = typeof(MyPoint<>);
    Trace.WriteLine("Type of MyPoint<> is " + tpUnbounded1.ToString());     // Output: Type of MyPoint<> is Generics.MyPoint`1[T]

    Type tpUnbounded2 = typeof(LinkedList<,>);                           // Note use of <,>
    Trace.WriteLine("Type of LinkedList<,> is " + tpUnbounded2.ToString()); // Output: Type of LinkedList<,> is Generics.LinkedList`2[K,I]
}

Type class has new methods and properties to provide reflection information about the generic aspect of a generic type:

public void TestType2(T t)
{
    MyPoint<T> pt = new MyPoint<T>();
    Type tpPoint = pt.GetType();
    Type[] aGenericArguments = tpPoint.GetGenericArguments();
    foreach (Type tp in aGenericArguments)
        Trace.WriteLine(tp.FullName);                      // Output: System.Int32

    Type tpGenericTypeDef = tpPoint.GetGenericTypeDefinition();
    Trace.WriteLine(tpGenericTypeDef.FullName);            // Output: Generics.MyPoint`1
}

And when defining an attribute (by deriving from System.Attribute), you can instruct the compiler that the attribute should target generic type parameters (i.e., the T in Stack<T>):

[AttributeUsage(AttributeTargets.GenericParameter)]
public class GenericParamAttribute : System.Attribute
{
    /* ... */
}

Finally, note that you cannot define generic attributes, but an attribute class can take advantage of generics by using generic types or define helper generic methods just like any other class:

public class SomeAttribute<T> : Attribute   // error CS0698: A generic type cannot derive from 'System.Attribute' because it is an attribute class
{ /* ... */ }

Generics and the .NET Framework

This section covers areas of the .NET Framework that are now using generics.

System.Array

System.Array class has been extended with many generic static methods. These generic static methods are designed to streamline and automate common tasks of working with arrays. Such common tasks include:

System.Array's generic static methods all work with the following four generic delegates (defined in the System namespace):

The following examples illustrate the points above:

class Arrays<T> where T : class
{
    private T[] m_aData = null;

    // Initializes the array of generic data
   
public Arrays(T[] data)
    {
        m_aData = data;
    }

    public void TestPredicate()
    {
        // In C# 2, you can directly assign a delegate variable with a method reference without having to use new
       
Predicate<T> delIsNonNull = IsNonNull;
        T[] aNonNulls = Array.FindAll<T>(m_aData, delIsNonNull);         // aNonNulls contains 1, 2, 4
    }

    public void TestAction()
    {
        // In C# 2, you can directly assign a delegate variable with a method reference without having to use new
       
Action<T> delDoTrace = DoTrace;
        Array.ForEach<T>(m_aData, delDoTrace);
    }

    public void TestConverter()
    {
        // In C# 2, you can directly assign a delegate variable with a method reference without having to use new
        Converter<T, int> delConvertToInt = Convert;
        int[] aConvertedData = Array.ConvertAll<T, int>(m_aData, delConvertToInt); // aConvertedData contains 1,2,0,4
    }

    // Predicate delegate function
   
private bool IsNonNull(T element)
    {
        bool bRet = (element != null) ? true : false;
        return bRet;
    }

    // Action delegate function
   
private void DoTrace(T element)
    {
        if (element != null)
            Trace.WriteLine("element " + element.ToString());
    }

    // Converter delegate function
   
private int Convert(T source)
    {
        if (source != null)
            return System.Convert.ToInt32(source);
        else
            return 0;
    }
}

string[] aData = { "1", "2", null, "4" };
Arrays<string> ob = new Arrays<string>(aData);
ob.TestPredicate();
ob.TestAction();
ob.TestConverter();

Similar generic methods are also available on the class List<T> defined in System.Collections.Generic namespace. These methods use the same four generic delegates mentioned above.

Generic Collections

The data structures in System.Collections are all object-based and hence inheriting the two problems mentioned above, namely inferior performance and lack of type safety. This problem is alleviated with a set of generic collections from the System.Collections.Generic namespace.  For example, now there are generic Stack<T>, Queue<T>, IEnumerable<T>, IEnumerator<T>, IList<T> and so on. Also among the generic collections is Dictionary<K,V> which is equivalent to the non-generic Hashtable class.

Note that all generic collections implement IEnumerable<T> which means that all generic collections can be be used in a C# foreach statement:

// Create a generic dictionary with int and string type arguments
Dictionary<int, string> htID_To_Name = new Dictionary<int, string>();
htID_To_Name.Add(1, "generics");
htID_To_Name.Add(2, "ADO.NET");
htID_To_Name.Add(3, "iterators");

// Previously, to iterator over a dictionary you would use the following statement:
// foreach (DictionaryEntry de in htIDToName ) { ... }
// Now note the usage of KeyValuePair instead

foreach (KeyValuePair< int, string> kvp in htID_To_Name)
{
    Trace.WriteLine("Key: " + kvp.Key + ". Value: " + kvp.Value);
}

Generics and Serialization

Generic types can be serialized, i.e. marked with the [Serializable] attribute:

[Serializable]
internal class Line<T>
{
    public T X = default(T);
    public T Y = default(T);
}

Serializing an instance of a type means persisting the state of that instance into some stream. However, for generic types, the following points must be observed:

These two points are illustrated in the example below:

public void TestSerialization()
{
    try
    {
        // Create a bounded generic type (i.e., a generic type that is bounded to a type)
       
Line<int> line = new Line<int>();
        line.X = 10;
        line.Y = 20;

        // Serialize the bounded generic type
       
MemoryStream ms = new MemoryStream();
        IFormatter formatter = new BinaryFormatter();
        formatter.Serialize(ms, line);

        // Deserialize the generic type back to the original bounded generic type
       
ms.Seek(0, SeekOrigin.Begin);
        Line<int> line2= (Line<int>)formatter.Deserialize(ms);

        // Deserializing into a different generic type raises an exception: Unable to cast object of
        // type 'Generics.Line`1[System.Int32]' to type 'Generics.Line`1[System.Double]'.

        Line<double> line2= (Line<double>)formatter.Deserialize(ms);         // Error
    }
    catch (Exception ex)
    {
        Trace.WriteLine(ex.Message);
    }
}

In the code snippet above, IFormatter is object-based. You can implement a generic formatter to do away with casting and possible errors due to incorrect casting to different generic types:

internal interface IGenericFormatter<T>
{
    void Serialize(Stream serializationStream, T graph);
    T Deserialize(Stream serializationStream);
}

public class GenericFormatter<T, F> : IGenericFormatter<T> where F : IFormatter, new()
{
    IFormatter formatter = new F();

    // IGenericFormatter implementation
    public void Serialize(Stream serializationStream, T graph)
    {
        formatter.Serialize(serializationStream, graph);
    }

    public T Deserialize(Stream serializationStream)
    {
        return (T)formatter.Deserialize(serializationStream);
    }
}

public void TestSerialization2()
{
    try
    {
        // Create a bounded generic type (i.e., a generic type that is bounded to a type)
       
Line<int> line = new Line<int>();
        line.X = 10;
        line.Y = 20;

        // Serialize the bounded generic type using a generic formatter
        System.IO.MemoryStream ms = new MemoryStream();
        GenericFormatter<Line<int>, BinaryFormatter> formatter = new GenericFormatter<Line<int>, BinaryFormatter>();
        formatter.Serialize(ms, line);

        // Deserialize the generic type back to the original bounded generic type
        ms.Seek(0, SeekOrigin.Begin);
        Line<int> line2 = formatter.Deserialize(ms);            // No casting necessary
    }
    catch (Exception ex)
    {
        Trace.WriteLine(ex.Message);
    }
}

Generics and Remoting

As expected, you can define and deploy remotable classes that use generics:

Defining Remotable Objects

public class MyServer<T> : MarshalByRefObject where T : MarshalByRefObject
{
    /* ... */
}

The derivation constraint is necessary because you can only access MyServer over remoting only when the generic type parameter T is a marshalable object.

Configuring Remotable Objects

As for configuration, you can use administrative or programmatic configuration as follows:

Administrative Configuration

Here you need to specify the exact type arguments to use instead of the generic type parameters.  For example, the following shows how to configure a client-activated object CAO called MyServer<T> declared in the namespace RemoteServer and implemented in ServerAssembly.dll to use int and string:

<service>
    <activated type="RemoteServer.MyServer[[System.Int32],[System.String]], ServerAssembly" />
</service>

Programmatic Configuration

Here you configure remotable object similar to .NET 1.1, except when defining the type of the remote object you have to provide type arguments instead of the generic type parameters:

RemotingConfiguration.RegisterWellKnownClientType( typeof(MyServer<int>), strURL );

Creating Remotable Objects

And as for creating remotable objects, simply provide the type arguments:

// Using new
MyServer<int> ob = new MyServer<int>();

// Using Activator.GetObject
MyServer<int> ob = (MyServer<int>)Activator.GetObject( typeof(MyServer<int>), strURL );

Miscellaneous

Covariance, Contra-variance, and Invariance

Generic types are not covariant. A generic type with a specified type argument MyClass<MyDerived> cannot be substituted with another generic type MyClass<MyDerived> that uses a type argument that is the base type for the first type argument. For example, the following does not compile:

class MyBase             { /* ... */ }
class MyDerived : MyBase { /* ... */ }
class MyClass<T>         { /* ... */ }

MyClass<MyBase> ob = new MyClass<MyDerived>();    // error CS0029: Cannot implicitly convert type 'MyClass<MyDerived>' to 'MyClass<MyBase>'

However, generic constraints are covariant. Consider this example:

class MyBase                       { /* ... */ }
class MyDerived : MyBase           { /* ... */ }
class MyClass<T> where T : MyBase  { /* ... */ }

MyClass<MyDerived> ob = new MyClass<MyDerived>();        // Compiles because MyDerived inherits from MyBase

Generic types are not contra-variant. For example, it is true that MyClass<MyBase> is not the base type of MyClass<MyDerived>:

Debug.Assert( typeof(MyClass<MyBase>) != typeof(MyClass<MyDerived>).BaseType );        // Assertion is true

Finally, generics are invariant because there is no relationship between two generic types with different type arguments even if those type arguments do have an is-a relationship. For example, List<int> has nothing to do with List<object> even though an int is-a object.

Deriving from a Generic Type Parameter

A generic class cannot derive from its type parameter:

public class MyClass<T> : T    // error CS0689: Cannot derive from 'T' because it is a type parameter
{ /* ... */ }

Generics and C++ templates

Generics are similar in concept to C++ templates: both allow clients to specify types for data structures or utility classes. Also both offer productivity and type-safety benefits. However, there are two main differences between generics and C++ templates:

  1. Programming model
    Generics can provide enhanced type-safety compared to C++ templates through the use of constraints, but there are a few things that generics cannot do such as using operators (there is no way to constraint a type parameter to support an operator).
     
  2. Underlying implementation
    Both can templates and generics can incur some code bloat and both have mechanism to limit that bloat. In C++ templates, instantiating a template with a set of types instantiates only the methods actually used. All other methods that result in identical code are automatically merged by the compiler to prevent duplication. With generics, instantiating a generic type with a set of types instantiates all of its methods, but only once for all reference type arguments. Bloat in generics only comes from value types because the CLR instantiates a generic type separately for each value type argument.

Generics vs. Interfaces

Interfaces and generics serve different purposes. Interfaces are about defining a contract between service a consumer and a service provider. As long as the consumer programs against the interface, it can use any other service provider that supports the same interface. This allows switching service providers without affecting client code.

On the other hand, generics are about defining and implementing a service without committing to the actual types used. As such, interfaces and generics are not mutually exclusive, in fact they complement each other. Good programming practices often combine interfaces and generics. For example, consider the IList<T> interface:

public interface IList<T>
{
    void Insert( int index, T item );
    void RemoveAt( int index );
    T this[int index] { get; set; }
}

IList<T> can then be implemented by any class:

public class SortedList<T> : IList<T> { /* ... */ }

public class LinkedList<T> : IList<T> { /* ... */ }

You can now program against IList<T> using both different implementations and different type arguments:

IList<int>    list1 = new SortedList<int>();
IList<double> list2 = new LinkedList<double>();