Generics in C#

 

Generics permit classes, structs, interfaces, delegates, and methods to be parameterized by the types of data they store and manipulate. C# generics will be immediately familiar to users of generics in Eiffel or Ada, or to users of C++ templates, though they do not suffer many of the complications of the latter.

 

Why Generics? 

Without generics, general purpose data structures can use type object to store data of any type. For example, the following simple Stack class stores its data in an object array, and its two methods, Push and Pop, use object to accept and return data, respectively. 

public class Stack 

{ 

        object[] items; 

        int count; 

        public void push (object item){.....} 

        public object pop (){....} 

} 

While the use of type object makes the Stack class very flexible, it is not without drawbacks. For example, it is possible to push a value of any type, such a Customer instance, onto a stack. However, when a value is retrieved, the result of the pop method must explicitly be cast back to the appropriate type, which is tedious to write and carries a performance penalty for run-time type checking: 

Stack stack = new Stack(); 

stack.push(new Customer()); 

Customer c = (customer)Stack.pop();

If a value of a value type, such as an int, is passed to the Push method, it is automatically boxed. When the int is later retrieved, it must be unboxed with an explicit type cast.

Stack stack = new Stack();

stack.push(3); 

int i = (int)stack.pop();

such boxing and unboxing operations add performance overhead since they involve dynamic memory allocations and run-time type checks. 

A further issue with the Stack class is that it is not possible to enforce the kind of data placed on a stack. Indeed, a Customer instance can be pushed on a stack and then accidentally cast it to the wrong type after it is retrieved.

Stack stack = new Stack(); 

stack.push(new Customer()); 

string s = (String)stack.pop();

While the code above is an improper use of the Stack class, the code is technically speaking correct and a compile-time error is not reported. The problem does not become apparent until the code is executed, at which point an InvalidCastException is thrown.

The Stack class would clearly benefit from the ability to specify its element type. With generics, that becomes possible.

 

Creating and using Generics:

Generics provide a facility for creating types that have type parameters. The example below declares a generic Stack class with a type parameter T. The type parameter is specified in <and> delimiters after the class name. Rather than forcing conversions to and from object, instances of Stack <T> accept the type for which they are created and store data of that type without conversion. The type parameter T acts as a placeholder until an actual type is specified at use. Note that T is used as the element type for internal item array, the type for the parameter to the Push method, and the return type for the pop method:

public class stack<T> 

{ 

        T[] items; 

        int count; 

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

        public T pop{.....} 

}

 

when the generic class Stack<T> is used, the actual type to substitute for T is specified. In the following example,  int is given as the type argument for T. 

Stack<int> stack =new Stack<int>(); 

stack.push(3); 

int x= stack.pop();

The Stack<int> type is called a constructed type.In the Stack<int> type, every occurrence of T is replaced with the type argument int. When an instance of Stack<int> is created, the native storage of the items array is an int[] rather than object[], providing substantial storage efficiency compared to the non-generic Stack. Likewise, the push and pop methods of a Stack<int> operate on int 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’ re retrieved.

 

Generics provide strong typing, meaning for example that it is an error to push an int onto a stack of Customer objects. Just as a Stack<int> is restricted to operate only on int values, so is Stack<customer> restricted to Customer objects, and the compiler will report errors on the last two lines of the following example.

 

Stack<Customer> stack= new Stack<Customer>(); 

stack.push(new Customer()); 

Customer c=stack.Pop(); 

Stack.push(3);   //Type mismatch error 

int x=stack.pop();  //Type mismatch error

 

Generic type declarations may have any number of type parameters. The Stack<T> example above has only one type parameter, but a generic Dictionary class might have two type parameters, one for the type of the keys and one for the type of the values.

 

public class Dictionary<K, V> 

{ 

        public void Add(K Key, V value)  {....} 

        public V this[K Key]{....} 

}

 

When Dictionary<K, V> is used, two type arguments would have to be supplied. 

 

Dictionary<string, Customer>  dict = new Dictionary<string, Customer>(); 

Dict.add("John", new Customer()); 

Customer c= dict ["John"];