In programming we often deal with collections of different classes. It can be a collection of person objects, pupil objects, teacher objects or whatever type of objects you’re handling. A lot of the operations we perform on these collections are very often similar to each other. You can choose to implement a custom collection class to handle each type of objects, but this approach is cumbersome and creates a lot of redundant code. You can also choose to store your objects as the object base class System.Object and the cast the objects to the correct class when you need to. However this approach generates a lot of overhead, when casting objects to and from the different classes and on top of that it also increases the risk of runtime errors considerably, because we sometime try to cast an object to the wrong class.

Generics provide an elegant and easy solution to this problem. Generics allow us to postpone the declaration of what type a class contains to runtime instead of compile time. Take this simple example:

class Gen<T>

    {

        public T t1;

        public T t2;

 

        public Gen(T pT1, T pT2)

        {

            this.t1 = pT1;

            this.t2 = pT2;

        }

    }

class Program

    {

        static void Main(string[] args)

        {

            Gen<string> mGen = new Gen<string>("Hello", "World");

            Gen<int> mGen2 = new Gen<int>(12, 13);

            Console.WriteLine(mGen.t1 + mGen.t2);

            Console.WriteLine(mGen2.t1 + mGen2.t2);

            Console.ReadKey();

        }

    }

The first declaration is the generic class. Note the angle brackets after the class name. Here the class has the possibility of having 1 type, but it is also possible for a generic class to have more than one type eg. Class Gen<T,U> would yield that the class had 2 different types (not necessarily 2 different types). When we run our small program we create to different instances of our Gen class. One requires strings and the other requires integers. When we use the + operator on the two instances of Gen, it is used accordingly to the type declared in the generic class definition. So the program results in the following result:

HelloWorld

25

Of course this is a very simple example and we soon run into problems if we try to use methods that are not inherited from System.Objects. If we try to call the split function on either t1 or t2, the code will compile for mGen but not for mGen2. This is because int does not have a method called split.

This problem is solved very elegantly by using constraints. When you apply constraints on a generic class, you place a requirement o the types that the consuming code can substitute for the generic parameter. The 2 most used types of constraints are Interface constraints and Base class constraints. Basically you can set up a constraint that the generic parameter must implement a certain interface or inherit from a given base class.

An example of this could be if we have a Person interface, which states that the implementing class must have a function called Income. We have 2 classes that implement the interface – Pupil and Teacher. The 2 classes have different implementations of how the income is calculated. If we have a generic class that handles storage which have the constraint that the type of the generic type parameter implements the interface, then the compiler will ensure that no matter what object is returned from the collection, then we can call the Income method on this object.

In this simple example I declare a small interface and a generic class which has the constraint that the parameter type of the generic class implements the Person interface.

interface Person

    {

        int Income();

    }

class PersonCollection<T>

        where T : Person

    {

        private List<T> cCollection;

 

        public PersonCollection ()

        {

            cCollection = new List<T>();

        }

 

        public void Add(T pElement){

            cCollection.Add(pElement);

        }

 

        public T Get(int pIndex)

        {

            return cCollection[pIndex];

        }

    }

Now we have a collection class that can store all classes that implements the Person interface and that means that we have a guarantee from the compiler, that there is a Income method defined no matter what type is stored in the generic collection class. Observe that I’m using the List<T> class, which is one of the generic collection classes which comes with .NET.

Pretty neat stuff, huh?