Contents
Introduction
For quite sometime now, I am involved in the development of application software using the .NET Framework version 1.1. But there was one thing that .NET 1.1 really lacked. The support for something like 'Templates' found in the good old(?) C++. The support for the concept of type parameters, which makes it possible to design classes which take in a generic type and determine the actual type later on.
This means that by using a generic type parameter T
, you can write a single class MyList<T>
and the client code can use it as MyList<int>
, MyList<string>
or MyList<MyClass>
without any risk of runtime casts or boxing operations.
My dear friends let me introduce you to the concept of 'Generics', which is included in .NET Framework version 2.0, and which can be considered very close to Templates in C++.
Version 2.0 of the .NET Framework introduces a new namespace viz. System.Collections.Generic
, which contains the classes that support this concept, like the List
, Queue
, Stack
, LinkedList
and many more which you can use effectively in your programs.
Advantages of Generics
In the earlier versions, before .NET 2.0, generalization was accomplished by casting types to and from the universal base type System.Object
. Generics provide a solution to this limitation in the common language runtime and the C# language. This limitation can be demonstrated with the help of the ArrayList
collection class from the .NET Framework base class library. ArrayList
is a highly convenient collection class that can be used without any modifications to store any reference or value type.
ArrayList list1 = new ArrayList();
list1.Add(3);
list1.Add(105);
ArrayList list2 = new ArrayList();
list2.Add("First item.");
list2.Add("Second item");
But this convenience comes at a cost. Any reference or value type that is added to an ArrayList
is implicitly typecast to System.Object
. If the items are value types, they must be boxed when added to the list, and unboxed when they are retrieved. The casting, boxing and unboxing operations degrade performance; the effect of boxing and unboxing can be quite significant in scenarios where you must iterate through large collections.
So, what we need is, flexibility of the ArrayList
but it should be more efficient and should provide some ways of type checking by the compiler, without sacrificing on the reusability of that class with different data types. An ArrayList
with a type parameter? That is precisely what generics provide. Generics would eliminate the need for all items to be type cast to Object and would also enable the compiler to do some type checking.
In the generic List<T>
collection, in System.Collections.Generic
namespace, the same operation of adding items to the collection looks like this:
List<int> list1 = new List<int>();
list1.Add(3);
list1.Add("First item");
For the client code, the only added syntax with List<T>
compared to ArrayList
is the type argument in the declaration and in instantiation. In return for this slightly greater coding complexity, you can create a list that is not only safer than ArrayList
, but also significantly faster, especially when the list items are value types.
Generic Classes
Generic classes encapsulate operations that are not specific to any particular data type. The most common use for generic classes is with the collections like linked lists, hash tables, stacks, queues, trees and so on where operations such as adding and removing items from the collection are performed in more or less the same way regardless of the type of the data being stored.
public class Node <T>
{
T head;
T next;
}
Here, T
is the type parameter. We can pass any data type as parameter.
This class can be instantiated like this:
Node<string> node = new Node<string>();
This will tell the compiler that the properties, head and next are of type string
. Instead of string
, you can substitute this with any data type.
Generic Methods
A generic method is a method that is declared with a type parameter.
void Swap<T>( ref T left, ref T right)
{
T temp;
temp = left;
left = right;
right = temp;
}
The following code example shows how to call the above method:
int a = 1;
int b = 2;
Swap <int> (a, b);
You can also omit the type parameter because the compiler will automatically identify it for you. The following is also a valid call to the same method:
Swap (a, b);
Write your own Generic class
The following example demonstrates how you can write your own generic classes.
The example shown below is a simple generic linked list class for demonstration purpose:
using System;
using System.Collections.Generic;
public class MyList<T>
{
private Node head;
private class Node
{
private Node next;
private T data;
public Node(T t)
{
next = null;
data = t;
}
public Node Next
{
get { return next; }
set { next = value; }
}
public T Data
{
get { return data; }
set { data = value; }
}
}
public MyList()
{
head = null;
}
public void AddHead(T t)
{
Node n = new Node(t);
n.Next = head;
head = n;
}
public IEnumerator<T> GetEnumerator()
{
Node current = head;
while (current != null)
{
yield return current.Data;
current = current.Next;
}
}
}
Notice the declaration of the above class :
public class MyList<T>
T
is the parameter type. Throughout the above code, the data type for the Node
is T
rather than any specific types like int
or string
or any other class. This gives flexibility to the programmer to use this class with any data type he wishes to use.
The following code example shows how the client code uses the generic MyList<T>
class to create a list of integers. Simply by changing the type argument, the code below can be easily modified to create lists of string
s or any other custom type:
class Program
{
static void Main(string[] args)
{
MyList<int> list = new MyList<int>();
for (int x = 0; x < 10; x++)
list.AddHead(x);
foreach (int i in list)
Console.WriteLine(i);
Console.WriteLine("Done");
}
}
Okay. I think you have got a hang of the generics by now, right? Anybody who has worked with templates in C++ would find this almost similar.
How generics are handled by the .NET runtime
When a generic type or method is compiled into MSIL, it contains metadata that identifies it as having type parameters. How this MSIL which contains generic type is used is different based on whether or not the supplied type parameter is a value or reference type.
When a generic type is first constructed with a value type as parameter, the runtime creates a specialized generic type with the supplied parameter or parameters substituted in the appropriate places in the MSIL. Specialized generic types are created once for each of the unique value type used as parameter.
For example, suppose your program code declared a Stack
constructed of integers, like this:
Stack<int> stack;
At this point, the runtime generates a specialized version of the Stack
class with the integer substituted appropriately as its parameter. Now, whenever your program code uses a stack of integers, the runtime reuses the generated specialized Stack
class. In the following example, two instances of a stack of integers are created, and they share a single instance of the Stack<int>
code:
Stack<int> stackOne = new Stack<int>();
Stack<int> stackTwo = new Stack<int>();
However, if at another point in your program code another Stack
class is created but with a different value type such as a long
or a user-defined structure as its parameter, then the runtime generates another version of the generic type, this time substituting a long
in the appropriate places in the MSIL. Conversions are no longer necessary because each specialized generic class natively contains the value type.
Generics work a bit differently for reference types. The first time a generic type is constructed with any reference type, the runtime creates a specialized generic type with the object references substituted for the parameters in the MSIL. Then, each time a constructed type is instantiated with a reference type as its parameter, regardless of its type, the runtime reuses the previously created specialized version of the generic type. This is possible because all references are the same size.
For example, suppose you had two reference types, a Customer
class and an Order
class, and that you created a stack of Customer
types:
Stack<Customer> customers;
At this point, the runtime generates a specialized version of the Stack
class that, instead of storing data, stores object references that will be filled in later. Suppose the next line of code creates a stack of another reference type, called Order
:
Stack<Order> orders = new Stack<Order>();
Unlike the value types, another specialized version of the Stack
class is not created for the Order
type. Rather, an instance of the specialized version of the Stack
class is created and the orders variable is set to reference it. Suppose you then encountered a line of code to create a stack of a Customer
type:
customers = new Stack<Customer>();
As with the previous use of the Stack
class created with the Order
type, another instance of the specialized Stack
class is created, and the pointers contained therein are set to reference an area of memory the size of a Customer
type. The C# implementation of generics greatly reduces code bloat by reducing the number of specialized classes created by the compiler for generic classes of reference types to just one.
How does C# generics differ from C++ templates
C# Generics and C++ templates are both language features that provide support for parameterized types. However, there are many differences between the two. At the syntax level, the C# generics are a simpler approach to parameterized types without any of the complexities of C++ templates. In addition, C# does not attempt to provide all the functionality that C++ templates provide.
At the implementation level, the primary difference is that the C# generic type substitutions are performed at runtime and generic type information is preserved for the instantiated objects.
Conclusion
Generics are a great way of writing classes that combine reusability, type safety and efficiency. Generics are commonly used with collections. .NET 2.0 has introduced a new namespace called System.Collections.Generic
which contains classes that support generics.