C# Tutorial - Generics Programming in C#

Generics Programming in C#

Generics were added to version 2.0 of the C# language and the common language runtime (CLR). Generics introduce to the .NET Framework the concept of type parameters, which 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. For example, by using a generic type parameter T you can write a single class that other client code can use without incurring the cost or risk of runtime casts or boxing operations, as shown here:

public class Generic
{
    public T Field;
}

When you create an instance of a generic class, you specify the actual types to substitute for the type parameters. This establishes a new generic class, referred to as a constructed generic class, with your chosen types substituted everywhere that the type parameters appear. The result is a type-safe class that is tailored to your choice of types, as the following code illustrates.

public static void Main()
{
    Generic g = new Generic();
    g.Field = "A string";
    //...
    Console.WriteLine("Generic.Field           = \"{0}\"", g.Field);
    Console.WriteLine("Generic.Field.GetType() = {0}", g.Field.GetType().FullName);
}

Generics Overview

  • Use generic types to maximize code reuse, type safety, and performance.
  • The most common use of generics is to create collection classes.
  • The .NET Framework class library contains several new generic collection classes in the System.Collections.Generic namespace. These should be used whenever possible instead of classes such as ArrayList in the System.Collections namespace.
  • You can create your own generic interfaces, classes, methods, events and delegates.
  • Generic classes may be constrained to enable access to methods on particular data types.
  • Information on the types that are used in a generic data type may be obtained at run-time by using reflection.

where (C# generic type constraint)

The where clause in a generic definition specifies constraints on the types that are used as arguments for type parameters in a generic type, method, delegate, or local function. Constraints can specify interfaces, base classes, or require a generic type to be a reference, value, or unmanaged type. They declare capabilities that the type argument must possess.

For example, you can declare a generic class, MyGenericClass, such that the type parameter T implements the IComparable interface:

public class AGenericClass where T : IComparable { }

The where clause can also include a base class constraint. The base class constraint states that a type to be used as a type argument for that generic type has the specified class as a base class (or is that base class) to be used as a type argument for that generic type. If the base class constraint is used, it must appear before any other constraints on that type parameter. Some types are disallowed as a base class constraint: Object, Array, and ValueType.

The following table lists the types of generic constraints.

Constraint Description
where T : class Type must be reference type.
where T: struct Type must be value type.
where T: new() Type must have public parameterless constructor.
where T: <base class name> Type must be or derive from the specified base class
where T: <interface name> Type must be or implement the specified interface.
where T: U Type supplied for T must be or derive from the argument supplied for U.

Generic Methods - C# Programming

A generic method is a method that is declared with type parameters, as follows:

static void Swap(ref T a, ref T b)
{
    T temp;
    temp = a;
    a = b;
    b = temp;
}

The following code example shows one way to call the method by using int for the type argument:


public static void TestSwap()
{
    int a = 1;
    int b = 2;

    Swap(ref a, ref b);
    System.Console.WriteLine(a + " " + b);
}

You can also omit the type argument and the compiler will infer it. The following call to Swap is equivalent to the previous call:

Swap(ref a, ref b);

Use constraints to enable more specialized operations on type parameters in methods. This version of Swap, now named SwapIfGreater, can only be used with type arguments that implement IComparable.

void SwapIfGreater(ref T lhs, ref T rhs) where T : System.IComparable
{
    T temp;
    if (lhs.CompareTo(rhs) > 0)
    {
        temp = lhs;
        lhs = rhs;
        rhs = temp;
    }
}

Generic methods can be overloaded on several type parameters. For example, the following methods can all be located in the same class:

void DoWork() { }
void DoWork() { }
void DoWork() { }