Generics

Generics in C# allow you to generalise classes and methods to work with different underlying types. Generics are used in many of the C# collections such as List, Vector and Queue.

Using generics allows the compiler to enforce type-safety.

List, Vector etc. are examples of generic classes. It is also possible to define generic methods. An example might be a sort algorithm which can work equally well with integers, strings or any IComparable objects.

Example

An example of a simple collection is given below:

class GenericArray<T>
{
    private readonly T[] _data;

    public GenericArray(int size) => _data = new T[size];

    public T this[int i]
    {
        get => _data[i];
        set => _data[i] = value;
    }
}

class Program
{
    static void Main(string[] args)
    {
        var array = new GenericArray<double>(3);
        array[0] = 1.0;
        array[1] = 2.0;
        array[2] = 3.0;
    }
}

Note that we have used the expression body syntax to keep the code concise. Our class GenericArray takes a type parameter T which defines the type of object we want to store. We have defined a custom indexer so that we can access the array elements using square brackets.

Constraints

By default, any type can be used as a type parameter. However, where this is not appropriate, constraints can be specified on the type parameters.

For example, suppose our GenericArray class has a Sort() function. It would therefore be better if we stipulated that the type parameter implements the IComparable interface.

This can be achieved by changing the class definition as shown:

class GenericArray<T> where T : IComparable
{
   ...
}

Variance

From C# 4, type parameters can be marked as covariant or contravariant.

If a type parameter is covariant, you can use a more specialised (i.e. derived) type than the one specified. The out keyword designates a type parameter as covariant.

If a type parameter is contravariant, you can use a more general type than the one specified (i.e. a superclass). The in keyword designates a type parameter as contravariant.

Here is an example using the Image and Bitmap classes from the System.Drawing namespace. Note that Bitmap is derived from Image.

interface IGettable<out T>
{
    T Get();
}

interface ISettable<in T>
{
    void Set(T value);
}

class Property<T> : IGettable<T>, ISettable<T>
{
    private T _value;

    public T Get() => _value;
    public void Set(T value) => _value = value;
}

class Program
{
    static void Main(string[] args)
    {
        // Covariance
        Property<Bitmap> bitmapProp = new Property<Bitmap>();
        IGettable<Image> imageProp = bitmapProp;

        // Contravariance
        Property<Image> imgProp = new Property<Image>();
        SetBitmap(imgProp);
    }

    static void SetBitmap(ISettable<Bitmap> bitmapProp)
    {
        bitmapProp.Set(new Bitmap(100, 100));
    }
}