Explained Generics in .NET

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.
// The .NET Framework 1.1 way of creating a list

    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:
// The .NET Framework 2.0 way of creating a list

    List<int> list1 = new List<int>();
    list1.Add(3); // No boxing, no casting

    list1.Add("First item"); // Compile-time error
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> //type parameter T in angle brackets

{
    private Node head;
    
    // The nested class is also generic on T.

    private class Node 
    {
        private Node next;
        //T as private member data type:

        private T data; 
        //T used in non-generic constructor:

        
        public Node(T t) 
        {
            next = null;
            data = t;
        }

 
        public Node Next
        {
            get { return next; }
            set { next = value; }
        }
 
        //T as return type of property:

        public T Data 
        {
            get { return data; }
            set { data = value; }
        }
    }
 
    public MyList()
    {
        head = null;
    }
 
    //T as method parameter type:

    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 strings or any other custom type:
class Program
    {
        static void Main(string[] args)
        {
            //int is the type argument.

            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.

0 comments:

Post a Comment