Using Generics In C#

C# Generics are used to create generic collections in C#. A C# Generic collection has certain key features such as compare, add, remove, find, and index items. In this C# Generics tutorial with code example, learn how to work with generics in .NET applications.

Generics in C# and .NET procedure many of the benefits of strongly-typed collections as well as provide a higher quality of and a performance boost for code. Generics are very similar to C++ templates but having a slight difference in such a way that the source code of C++ templates is required when a templates is instantiated with a specific type and .NET Generics are not limited to classes only. In fact they can also be implemented with Interfaces, Delegates and Methods. The detailed specification for each collection is found under the System.Collection.Generic namespace.
 

Boxing and Unboxing

 
.Net defines two major categories of data type termed value type and reference type to represent a variable. This is where boxing and unboxing are needed. Boxing is a mechanism to explicitly convert a value type to a reference type by storing the variable into System.Object; when you box the value the CLR allocates a new object into the heap and copies the value type's value into that instance. For example you have created a variable of int type as:
  1. int a = 20;  
  2. object b = a; //boxing  
The opposite operation is Unboxing which is the process of converting back the reference type into the value type. This process verifies that the receiving data type is equivalent to the boxed type as; 
  1. int c = (int)b; // unboxing  
The C# compiler sees the assignment from int to object and vice-versa. When this program is compiled and you examine the IL generated code via IL dissembler, you notice that the program respond by inserting a box instruction in the IL automatically when b is assigned the value of a and an unbox instruction when c is assigned the value b as in the following;
 
IL-opcode.jpg 
Figure 1.1 - IL opcode
 
The code loads the constant 20 and stores it in the local slot; it the loads the value 20 onto the stack and boxes it. Finally it loads the boxed 20 back onto the stack and unboxes it into an int.
 
There are  series of operations performed by .NET CLR, such as, first an object is allocated in the managed heap, then in boxing the value is transformed into the memory location and during unboxing the value is stored on the heap and must be transferred back to the stack. So the Boxing and Unboxing process has a significant importance in Generics from the performance point of view because this process is more resource-intensive rather than using Generics. 
 

Generic Classes

 
The Generic class can be defined by putting the <T> sign after the class name. It isn't mandatory to put the "T" word in the Generic type definition. You can use any word in the TestClass<> class declaration. 
  1. public class TestClass<T> { }  
The System.Collection.Generic namespace also defines a number of classes that implement many of these key interfaces. The following table describes the core class types of this namespace.
 
Generic class Description
Collection<T> The basis for a generic collection Comparer compares two generic objects for equality
Dictionary<TKey, TValue> A generic collection of name/value pairs
List<T> A dynamically resizable list of Items
Queue<T> A generic implementation of a first-in, first-out (FIFO) list
Stack<T> A generic implementation of a last-in, first-out (LIFO) list
 

Simple Generic Class Example

 
The following example shows a simple Generic type manipulation. The TestClass<T> defines an array of generic type with length 5. The Add() method is responsible for adding any type of objects into the collection and the Indexer property is an implementation of foreach statement iteration. Finally in the main class we instantiated the TestClass<T> class with an Integer type reference and adds some integer type elements into the collection using the Add() method.
  1. using System;  
  2. using System.Collections.Generic;  
  3.   
  4. namespace GenericApp  
  5. {  
  6.     public class TestClass<T>  
  7.     {  
  8.         // define an Array of Generic type with length 5  
  9.         T[] obj = new T[5];  
  10.         int count = 0;  
  11.   
  12.         // adding items mechanism into generic type  
  13.         public void  Add(T item)  
  14.         {  
  15.             //checking length  
  16.             if (count + 1 < 6)  
  17.             {  
  18.                 obj[count] = item;  
  19.   
  20.             }  
  21.             count++;  
  22.         }  
  23.         //indexer for foreach statement iteration  
  24.         public T this[int index]  
  25.         {  
  26.             get { return obj[index]; }  
  27.             set { obj[index] = value; }  
  28.         }   
  29.     }  
  30.     class Program  
  31.     {  
  32.         static void Main(string[] args)  
  33.         {  
  34.             //instantiate generic with Integer  
  35.             TestClass<int> intObj = new TestClass<int>();  
  36.   
  37.             //adding integer values into collection  
  38.             intObj.Add(1);  
  39.             intObj.Add(2);  
  40.             intObj.Add(3);     //No boxing  
  41.             intObj.Add(4);   
  42.             intObj.Add(5);  
  43.   
  44.             //displaying values  
  45.             for (int i = 0; i < 5; i++)  
  46.             {  
  47.                 Console.WriteLine(intObj[i]);   //No unboxing  
  48.             }  
  49.             Console.ReadKey();    
  50.         }  
  51.     }  
  52. }  
After building and running this program, the output of the program is as shown in the following;
 
Simple-Generic-Example.jpg 
Figure 1.2 - Simple Generic Example
 
There are some significant characteristics of Generic types that make them special to the conventional non-generics type as follows;
  • Type Safety
  • Performance
  • Binary Code reuse

Type Safety

 
One of the most significant features of Generics is Type Safety. In the case of the non-generic ArrayList class, if objects are used, any type can be added to the collections that can sometimes result in a great disaster. The following example shows adding an integer, string and object to the collection of an ArrayList type;
  1. ArrayList obj = new ArrayList();  
  2. obj.Add(50);  
  3. obj.Add("Dog");  
  4. obj.Add(new TestClass());  
Now, if the collection is iterated through the foreach statement using integer elements, the compiler accepts the code but because all the elements in the collection are not an integer, a runtime exception occurs; 
  1. foreach(int i in obj)  
  2. {  
  3.     Console.WriteLine(i);    
  4. }  
The rule of thumb in programming is that Errors should be detected as early as possible. With the generic class Test<T>, the generic type T defines what types are allowed. With the definition of Test<int>, only an integer type can be added to the collection. The compiler doesn't compile the code because the Add() method has invalid arguments as follows; 
  1. Test<int> obj = new Test<int>();  
  2. obj.Add(50);  
  3. obj.Add("Dog");            //compiler error  
  4. obj.Add(new TestClass());  //compiler error  

Performance

 
Another feature of Generics is performance. Using value types with non-generic collection classes result in boxing and unboxing overhead when a value type is converted to reference type and vice-versa.
 
In the following example, the ArrayList class stores objects and the Add() method is defined to store some integer type argument. So an integer type is boxed. When the value from ArrayList is read using the foreach statement, unboxing occurrs. 
  1. ArrayList  obj = new ArrayList();   
  2. obj.Add(50);    //boxing- convert value type to reference type  
  3. int x= (int)obj[0]; //unboxing  
  4. foreach(int i in obj)  
  5. {  
  6.    Console.WriteLine(i);   // unboxing  
  7. }  

Note

Generics are faster than other collections such as ArrayList.
 
Instead of using objects, a Generics type of the TestClass<T> class is defined as an int, so an int type is used inside the class that is generated dynamically from the compiler. Therefore boxing and unboxing no longer occurs as in the following; 
  1. TestClass<int> obj = new TestClass<int>();  
  2. obj.Add(50);    //No boxing  
  3. int x= obj[0]; // No unboxing  
  4. foreach(int i in obj)  
  5. {  
  6.    Console.WriteLine(i);   //No unboxing  
  7. }  

Binary Code reuse

 
Generics provide a kind of source code protection. A Generic class can be defined once and can be instantiated with many different types. Generics can be defined in one CLR supported language and used from another .NET language. The following TestClass<T> is instantiated with an int and string types: 
  1. TestClass<int> obj = new TestClass<int>();  
  2. obj.Add(50);  
  3.   
  4. TestClass<string> obj1 = new TestClass<string>();  
  5. Obj1.Add("hello");    

Generic Methods

 
While most developers will typically use the existing generic types within the base class libraries, it is certainly possible to build your own generic members and custom generic types.
 
The objective of this example is to build a swap method that can operate on any possible data type (value-based or reference-based) using a single type parameter. Due to the nature of swapping algorithms, the incoming parameters will be sent by reference via ref keyword.
  1. using System;  
  2. using System.Collections.Generic;  
  3.   
  4. namespace GenericApp  
  5. {  
  6.     class Program  
  7.     {  
  8.         //Generic method  
  9.         static void Swap<T>(ref T a, ref T b)  
  10.         {  
  11.             T temp;  
  12.             temp = a;  
  13.             a = b;  
  14.             b = temp;  
  15.         }  
  16.         static void Main(string[] args)  
  17.         {  
  18.             // Swap of two integers.  
  19.             int a = 40, b = 60;  
  20.             Console.WriteLine("Before swap: {0}, {1}", a, b);  
  21.   
  22.             Swap<int>(ref a, ref b);  
  23.   
  24.             Console.WriteLine("After swap: {0}, {1}", a, b);  
  25.   
  26.             Console.ReadLine();  
  27.         }  
  28.     }  
  29. }   
After compiling this Generic method implementation program, the output is as in the following;
 
Generic-Methods.jpg 
Figure 1.3 - Generic Methods
 

Dictionary

 
Dictionaries are also known as maps or hash tables. It represents a data structure that allows you to access an element based on a key. One of the significant features of a dictionary is faster lookup; you can add or remove items without the performance overhead.
 
.Net offers several dictionary classes, for instance Dictionary<TKey, TValue>. The type parameters TKey and TValue represent the types of the keys and the values it can store, respectively.
 

Simple Example of Dictionary

 
The following example demonstrates simple dictionary collections using Generics. In this program, a Dictionary type object is created that accepts an int as the key and a string as the value. Then we add some string values into the dictionary collection and finally displaying the dictionary collection elements.
  1. using System;  
  2. using System.Collections.Generic;  
  3.   
  4. namespace GenericApp  
  5. {  
  6.     public class Program    
  7.     {  
  8.         static void Main(string[] args)  
  9.         {  
  10.             //define Dictionary collection  
  11.             Dictionary<int,string> dObj = new Dictionary<int,string>(5);  
  12.   
  13.             //add elements to Dictionary  
  14.             dObj.Add(1,1,"Tom");    
  15.             dObj.Add(2,"John");  
  16.             dObj.Add(3, "Maria");  
  17.             dObj.Add(4, "Max");  
  18.             dObj.Add(5, "Ram");
  19.   
  20.             //print data  
  21.             for (int i = 1; i <= dObj.Count;i++)  
  22.             {  
  23.                 Console.WriteLine(dObj[i]);  
  24.             }  
  25.             Console.ReadKey();  
  26.         }  
  27.     }  
  28. }  
The following example portrays some more complexities by defining an addition class emp where we are overriding the ToString() method to display the name and salary of a particular employees. Later in the Main() method , a new Dictionary<TKey,TValue) instance is created, where the key is of type string and the value is of type emp. The constructor allocates a capacity of 2 elements. The emp objects and string value as a key is added to the dictionary collection. Finally the collection elements are iterated using a foreach statement and displayed on the screen.
  1. using System;  
  2. using System.Text;  
  3. using System.Collections.Generic;  
  4.   
  5. namespace GenericApp  
  6. {  
  7.     public class emp   
  8.     {  
  9.         private string name;  
  10.         private int salary;  
  11.    
  12.         public emp(string name,int salary)  
  13.         {  
  14.             this.name = name;  
  15.             this.salary = salary;   
  16.         }  
  17.         public override string ToString()  
  18.         {  
  19.             StringBuilder sb = new StringBuilder(200);  
  20.             sb.AppendFormat("{0},{1}",name,salary);  
  21.   
  22.             return sb.ToString();  
  23.         }  
  24.   
  25.     }  
  26.     public class Program    
  27.     {  
  28.         static void Main(string[] args)  
  29.         {  
  30.             //define Dictionary collection  
  31.             Dictionary<string, emp> dObj = new Dictionary<string, emp>(2);  
  32.   
  33.             //add elements to Dictionary  
  34.             emp tom = new emp("tom", 2000);  
  35.             dObj.Add("tom",tom);   // key,value  
  36.             emp john = new emp("john", 4000);  
  37.             dObj.Add("john",john);  
  38.   
  39.             //print data  
  40.             foreach(Object str in dObj.Values)  
  41.             {  
  42.                Console.WriteLine(str);  
  43.             }  
  44.    
  45.             Console.ReadKey();  
  46.         }  
  47.     }  
  48. }  

Queues

 
Queues are a special type of container that ensures the items are being accessed in a FIFO (first in, first out) manner. Queue collections are most appropriate for implementing messaging components. We can define a Queue collection object using the following syntax:
 
Queue qObj = new Queue();
 
The Queue collection property, methods and other specification definitions are found under the Sysyem.Collection namespace. The following table defines the key members;
 
System.Collection.Queue Members Definition
Enqueue() Add an object to the end of the queue.
Dequeue() Removes an object from the beginning of the queue.
Peek() Return the object at the beginning of the queue without removing it.
 
The following demonstrates a basic Queues type collection, adds some string type values into the collection and finally displays the entire items into the collection using the while statement.
  1. using System;  
  2. using System.Collections;  
  3.   
  4. namespace GenericApp  
  5. {  
  6.     class Program  
  7.     {  
  8.         static void Main(string[] args)  
  9.         {  
  10.             //Defines a Queue.  
  11.             Queue qObj = new Queue();  
  12.   
  13.             //adding string values into collection  
  14.             qObj.Enqueue("Tom");  
  15.             qObj.Enqueue("Harry");  
  16.             qObj.Enqueue("Maria");       
  17.             qObj.Enqueue("john");  
  18.   
  19.             //displaying collections  
  20.             while(qObj.Count !=0 )  
  21.             {  
  22.                 Console.WriteLine(qObj.Dequeue());  
  23.             }  
  24.   
  25.             Console.ReadKey();    
  26.         }  
  27.     }  
  28. }  

Stacks

 
A Stack collection is an abstraction of LIFO (last in, first out). We can define a Stack collection object using the following syntax:
 
Stack qObj = new Stack();
 
The following table illustrates the key members of a stack;
 
System.Collection.Stack Members Definition
Contains() Returns true if a specific element is found in the collection.
Clear() Removes all the elements of the collection.
Peek() Previews the most recent element on the stack.
Push() It pushes elements onto the stack.
Pop() Return and remove the top elements of the stack.

The following demonstrate a stack collection. First an array type object is referenced into the stack collection. Then the value of the elements in the collection are removed from the stack using the Pop() method and displayed on the screen.
  1. using System;  
  2. using System.Collections;  
  3.   
  4. namespace GenericApp  
  5. {  
  6.     class Program  
  7.     {  
  8.         static void Main(string[] args)  
  9.         {  
  10.             int[] iArray = new int[] {1,2,3,4,5,6,7,8,9,10 };  
  11.   
  12.             //Define a stack  
  13.             Stack sObj = new Stack(iArray);  
  14.   
  15.             Console.WriteLine("Total items="+sObj.Count);  
  16.   
  17.             //displaying collections  
  18.             for (int i = 0; i < sObj.Count;++i )  
  19.             {  
  20.                 Console.WriteLine(sObj.Pop());  
  21.             }  
  22.   
  23.             Console.ReadKey();    
  24.         }  
  25.     }  
  26. }  
In another example with a Generic implementation, 5 items are added to the stack with the Push() method. With the foreach statement, all the items are iterated using the IEnumerable interface. The enumerator of the stack does not remove the items; it just returns each item in a LIFO manner as in the following:
  1. using System;  
  2. using System.Collections.Generic;  
  3.   
  4. namespace GenericApp  
  5. {  
  6.     public class Program    
  7.     {  
  8.         static void Main(string[] args)  
  9.         {  
  10.             //define Dictionary collection  
  11.             Dictionary<int,string> dObj = new Dictionary<int,string>(5);  
  12.   
  13.            //add elements to Dictionary  
  14.             dObj.Add(1,"Tom");    
  15.             dObj.Add(2,"John");  
  16.             dObj.Add(3, "Maria");  
  17.             dObj.Add(4, "Max");  
  18.             dObj.Add(5, "Ram");  
  19.   
  20.             //print data  
  21.             foreach(string i in dObj.Values)  
  22.             {  
  23.                 Console.WriteLine(i);  
  24.             }  
  25.             Console.ReadKey();  
  26.         }  
  27.     }  
  28. } 

Further readings

 
Here are recommended articles on collections: