Académique Documents
Professionnel Documents
Culture Documents
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++.
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.
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.
Collapse
// The .NET Framework 2.0 way of creating a list
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.
Here, T is the type parameter. We can pass any data type as parameter.
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:
The example shown below is a simple generic linked list class for demonstration purpose:
using System;
using System.Collections.Generic;
public class MyList<T> //type parameter T in angle brackets
{
private Node head;
private T data;
//T used in non-generic constructor:
public Node(T t)
{
next = null;
data = t;
}
public T Data
{
get { return data; }
set { data = value; }
}
}
public MyList()
{
head = null;
}
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 strings or any other custom type:
class Program
{
static void Main(string[] args)
{
//int is the type argument.
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.
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:
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:
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:
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.
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