In .Net 1.1, if you wanted to have a collection of objects, such as a linked list, a simple array-like list, a queue, or, in fact, if you wanted to make a class that made use of some object internally, but you wanted to make your data structure available to multiple internal types, you had several options, but they all had some significant limitations.
Lets look at some examples. Suppose, for instance, you wanted to create a queue class, where you could have a queue of int, string, or any other type such as car, pet, or person (other classes you need to define elsewhere). Here are your options in .Net 1.1:
1. You could just use the generic 'object' type, as seen below. The big disadvantage here is that you have to be very careful adding and removing items, because you could easily add an object of the wrong type and you wouldn't know until run time when you hit an invalid cast exception.
public class Queue
{
public void enQueue(object o) { ... }
public object deQueue() { ... }
}
{
public void enQueue(object o) { ... }
public object deQueue() { ... }
}
2. You could create a intQueue class, a strQueue class, and so on. The intQueue class would look like the code below. You would get type safety, but the big disadvantage here is that anytime you want to create a queue of a new object, you have to create a new queue class, such as petQueue, carQueue, personQueue. That's a lot of duplicated code, which increases maintenance time and you could easily end up with inconsistencies between your queue classes.
public class intQueue
{
public void enQueue(int i) { ... }
public int deQueue() { ... }
}
{
public void enQueue(int i) { ... }
public int deQueue() { ... }
}
Enter .Net 2.0 and Generics. In .Net 2.0, you can define your generic queue class as such:
public class Queue<T>
{
public void enQueue(T t) { ... }
public T deQueue() { ... }
}
Notice that we use the generic placeholder of T. T is whatever class you want it to be, but T is defined when you actually declare / instantiate an object of the Queue class. Here is how you would make use of the class:
Queue<int> myIntQueue = new Queue<int>;
Notice how we put 'int' in between the < > signs. This signifies to the compiler that we are creating a queue of type int. So if we take the queue of int and try to add a string to it, like this:
myIntQueue.enQueue("ABC");
The compiler will throw an error, telling us that we can't add a string to a queue of int.
So there you go. Generics give us the ability to generically (i.e., in a template sort of way) declare data structures that encapsulate other types without knowing what the type is that we are encapsulating, and they provide compile time type safety.