Generics is the implementation of parametric polymorphism. Using parametric polymorphism, a method or data type can be written generically so that it can deal equally well with objects of various types. It is a way to make a language more expressible, while still maintaining full static type-safety.



Why use generics?

There are mainly two reasons to use generics. These are:
  • Performance – Collections that store objects use Boxing and Unboxing on data types. This uses a significant amount of overhead, which can give a performance hit. By using generics instead, this performance hit is removed.

  • Type Safety – There is no strong type information at compile time as to what is stored in the collection.

To understand these better, a simple example which does not use generics can be used. Consider the following class;
public class Stack

{

object[] store;

int size;

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

public object Pop() {...}

}
In order to use this class you can push any object onto the Stack, however when retrieving an object, an explicit cast is required.
Stack s = new Stack();

s.Push(28);

int j = (int)s.Pop(); //unboxing with explicit int casting
Such Boxing and Unboxing operations add performance overhead since they involve dynamic memory allocations and run-time type checks.
Now imagine what would happen if you pushed a string on the Stack as in the following code;
Stack s = new Stack();

s.Push("Hello World!"); // pushing the string

int i = (int)s.Pop(); // run-time exception will be thrown at this point
The code will compile, however the problem will not become visible until the code is executed, at which point an InvalidCastException will be thrown.

Generic equivalent

If the Stack class above was of a generic type, there would be a compile-time error when pushing the string onto the Stack. The generic version of the Stack class looks like the following code;
public class Stack

{

// Items are of type T, which is known when you create the object

T[] items;

int count;

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

// The type of the method Pop will be decided when you create the object

public T Pop() {...}

}
T is the type for the parameter. Within class you can use T as if it were a type, this could be anything from a simple data-type such as int to a complex object such as Car.
In the following example, string is given as the type argument for T:
Stack s = new Stack();

s.Push("Hello World!");

string str = s.Pop();
The Stack type is called a constructed type. In the Stack type, every occurrence of T is replaced with the type argument string. The Push and Pop methods of a Stack operate on string values, making it a compile-time error to push values of other types onto the Stack, and eliminating the need to explicitly cast values back to their original type when they are retrieved.
You can use parameterization not only for classes but also for interfaces, structs, methods and delegates.
interface IComparable 

 

struct HashBucket

 

static void Sort (T[] arr)

 

delegate void Action (T arg)



Terminology

There are several terms that are used when talking about generics, so it is worth mentioning them here so that when reading other documents, a better understanding can be obtained.

Type parameters

A type parameter refers to the parameter that is used in the definition of the generic type. In the generic version of the Stack class above, the class accepts one type parameter, T.

Type arguments

A type argument refers to the type you specify to use in place of the type parameter. In the following code segment, string is the type argument.
Stack s = new Stack();

Open types

Instead of referring to this as a class, generics consider the Stack as an "open type". The term "open" is meant to convey the idea that the type is not fully defined and is "open" to taking on multiple real representations.

Constructed types

A constructed type represents an instance of an open type. To create a constructed type from the open Stack type, use the following code:
Stack s;
Or
Stack s = new Stack();

Open constructed type

An open constructed type is created when at least one type argument is left unspecified, the parameter is still open to run-time definition. Consider the following type declaration:
public class LinkedList { ... }
The following are all open constructed types:
LinkedList myList_1; 

LinkedList myList_2;

LinkedList myList_3;

Closed constructed type

A closed constructed type is created by specifying all of the type arguments, therefore they are not open to run-time definition. Consider the following type declaration:
public class LinkedList { ... }
The following are all closed constructed types:
LinkedList myList_1; 

LinkedList myList_2;

LinkedList myList_3;



Constraints on type parameters

Constraints are used to restrict a generic class to the kinds of types that client code can use for type arguments. If code attempts to instantiate the class with a type that is not allowed by a constraint, then this will result in a compile-time error. Constraints are specified using the where keyword. The following table lists the five types of constraints:








Constraint Description
where T : struct The type argument, T, must be any value type except Nullable.
where T : class The type argument, T, must be a reference type, including any class, interface, delegate, or array type.
where T : new() The type argument, T, must have a public no-argument constructor. When used in conjunction with other constraints, the new() constraint must be specified last.
where T : The type argument, T, must be or derive from the specified base class.
where T : The type argument, T, must be or implement the specified interface. Multiple interface constraints can be specified. The constraining interface can also be generic.
where T : U The type argument, T, must be or derive from the argument supplied for U. This is called a naked type constraint.

Generic classes

Generic classes encapsulate operations that are not specific to a particular data type. Generic classes can be the base class to other classes, and can therefore define any number of virtual or abstract methods. Any derived type must abide by rules to ensure that the nature of the generic abstraction flows through to it.
  • If a non-generic class extends a generic class, the derived class must specify a type parameter:

// Generic list class.

public class GenericList

{

private List myList = new List();

}

 

// Derived type specifies the type parameter.

public class MyStringList : GenericList

{

}
  • If a generic base class defines generic virtual or abstract methods, the derived type must override the generic methods using the specified type parameter:

// Generic class with virtual method.

public class GenericList

{

private List myList = new List();

 

public virtual void PrintList(T data)

{

}

}

 

public class MyStringList : GenericList

{

// Derived method must substitute the type parameter

public override void PrintList(string data)

{

}

}
  • A generic derived type may reuse the type placeholder in its definition. Any constraints placed on the base class must be honoured by the derived type:

// Default constructor constraint.

public class GenericList where T : new()

{

private List myList = new List();

 

public virtual void PrintList(T data)

{

}

}

 

// Derived type must honour constraint.

public class MyReadOnlyList : GenericList where T : new()

{

public override void PrintList(T data)

{

}

}



Generic interfaces

With generic classes it is preferable to use generic interfaces, such as IComparable rather than IComparable, in order to avoid Boxing and Unboxing operations on value types. When an interface is specified as a constraint on a type parameter, only types that implement the interface can be used. For example:
public class SortedList : GenericList where T : System.IComparable

{

. . .

}

Defining a generic interface

You may define generic interfaces similar to generic classes (including constraints):
// Standard Interface

public interface IPrint

{

void Print();

}

 

// Generic Interface

public interface MyGenericInterface where T : IPrint

{

void Run(T t);

}

 

// Class that implements standard Interface

public class MyPrinter : IPrint

{

public void Print()

{

Console.WriteLine("hello");

}

}

 

// Class that implements Generic Interface

public class Print2 : MyGenericInterface

{

public void Run(MyPrinter t)

{

t.Print();

}

}

 

// Generic Class that implements Generic Interface with contraints

public class PrintDemo : MyGenericInterface where T : IPrint

{

public void Run(T t)

{

t.Print();

}

}
The .NET Framework has generic versions of all of the common interfaces.



Generic delegates

Suppose that we want a delegate that can update an item, but we are not sure what item is going to be updated. In this scenario, making the delegate generic is the best option. In order to explain this concept, the following code is based on an example from Tod Golding in .Net 2.0 Generics.
Imagine if you will that we are creating a piece of software that works with animals, namely cats and dogs. So here are two classes with some properties and a ToString() method:
public class Cat

{

private string m_name;

public string Name

{

get { return m_name; }

set { m_name = value; }

}

 

private int m_age;

public int Age

{

get { return m_age; }

set { m_age = value; }

}

 

public Cat(string name, int age)

{

m_name = name;

m_age = age;

}

 

public override string ToString()

{

return string.Format("The Cat named {0} is {1} years old",

m_name.ToString(), m_age.ToString());

}

}

 

public class Dog

{

private string m_name;

public string Name

{

get { return m_name; }

set { m_name = value; }

}

 

private double m_age;

public double Age

{

get { return m_age; }

set { m_age = value; }

}

 

public Dog(string name, double age)

{

m_name = name;

m_age = age;

}

 

public override string ToString()

{

return string.Format("The Dog named {0} is {1} years old",

m_name.ToString(), m_age.ToString());

}

}
We want a delegate that can update the age of either a cat or a dog.
public delegate void UpdateAnimal(T value);
As we will have many animals, a collection would be a good thing to have, and again to save on overhead and to give type safety, a generic collection would be best.
public class Animals : List

{

public void UpdateAnimals(UpdateAnimal updater)

{

List.Enumerator items = GetEnumerator();

 

while (items.MoveNext())

{

updater(items.Current);

}

}

 

public void Print()

{

List.Enumerator items = GetEnumerator();

 

while (items.MoveNext())

{

Console.WriteLine(items.Current.ToString());

}

}

}
Finally, we need a couple of methods to be called by the delegate to update each of the classes, and some code to test them.
class Program

{

static void Main(string[] args)

{

// Create Lists

Animals catList = new Animals();

catList.Add(new Cat("Tinkerbelle", 6));

catList.Add(new Cat("Felix", 3));

catList.Add(new Cat("Whiskers", 10));

catList.Add(new Cat("Tailz", 14));

 

Animals dogList = new Animals();

dogList.Add(new Dog("Rufus", 12.1));

dogList.Add(new Dog("Peeps", 3.2));

dogList.Add(new Dog("Hairy McClary", 6.3));

 



// Cats

catList.Print();

Console.WriteLine("---------------------------");

 

catList.UpdateAnimals(new UpdateAnimal(UpdateCatAge));

catList.Print();

 

Console.WriteLine("===========================");

 

// Dogs

dogList.Print();

Console.WriteLine("---------------------------");

 

dogList.UpdateAnimals(new UpdateAnimal(UpdateDogAge));

dogList.Print();

}

 

public static void UpdateCatAge(Cat cat)

{

cat.Age += 1;

}

 

public static void UpdateDogAge(Dog dog)

{

dog.Age += 0.1;

}

}
This gives the output:
Image:Gendel.jpg

Download this sample application (written in C# Express).

MSDN references


See also