Using Generics In C#

Generics in C# are a way to create code that can be used with different data types. This makes the code more versatile and reusable. Generics are declared using the <> symbol. For example, the following code declares a generic method called Print:

public static void Print<T>(T value)
{
    Console.WriteLine(value);
}

This method can be used to print any data. For example, the following code prints an integer and a string:

Print(10);
Print("Hello, world!");

Generics can also be used to create generic classes. For example, the following code declares a generic class called List:

public class List<T>
{
    private T[] items;

    public List()
    {
        items = new T[0];
    }

    public void Add(T item)
    {
        items = Array.Resize(items, items.Length + 1);
        items[items.Length - 1] = item;
    }

    public T Get(int index)
    {
        return items[index];
    }
}


This class can be used to store any data type. For example, the following code creates a list of integers and adds some numbers to it:

List<int> numbers = new List<int>();
numbers.Add(10);
numbers.Add(20);
numbers.Add(30);

Generics are a powerful tool that can be used to make code more versatile and reusable. They are a fundamental part of the C# language and are used in many different scenarios.

Here are some of the benefits of using generics in C#:

  • They make code more versatile. Generic code can be used with different data types, which makes it more reusable.
  • They make code more concise. Generic code can often be written more concisely than non-generic code.
  • They can improve performance. Generic code can sometimes improve performance by eliminating the need for type casts.
  • They can help to prevent errors. Generic code can help prevent errors by ensuring the correct data types are used.

If you are new to C#, I recommend learning about generics early on. They are a powerful tool that can make your code more versatile, concise, and efficient.

Generic Classes

The Generic class can be defined by putting the <T> sign after the class name. Putting the "T" word in the Generic type definition isn't mandatory. You can use any word in the TestClass<> class declaration. 

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 types with length 5. The Add() method is responsible for adding any objects into the collection, and the Indexer property implements foreach statement iteration. Finally, in the main class, we instantiated the TestClass<T> class with an Integer type reference and added some integer type elements into the collection using the Add() method.

using System;  
using System.Collections.Generic;  
  
namespace GenericApp  
{  
    public class TestClass<T>  
    {  
        // define an Array of Generic type with length 5  
        T[] obj = new T[5];  
        int count = 0;  
  
        // adding items mechanism into generic type  
        public void  Add(T item)  
        {  
            //checking length  
            if (count + 1 < 6)  
            {  
                obj[count] = item;  
  
            }  
            count++;  
        }  
        //indexer for foreach statement iteration  
        public T this[int index]  
        {  
            get { return obj[index]; }  
            set { obj[index] = value; }  
        }   
    }  
    class Program  
    {  
        static void Main(string[] args)  
        {  
            //instantiate generic with Integer  
            TestClass<int> intObj = new TestClass<int>();  
  
            //adding integer values into collection  
            intObj.Add(1);  
            intObj.Add(2);  
            intObj.Add(3);     //No boxing  
            intObj.Add(4);   
            intObj.Add(5);  
  
            //displaying values  
            for (int i = 0; i < 5; i++)  
            {  
                Console.WriteLine(intObj[i]);   //No unboxing  
            }  
            Console.ReadKey();    
        }  
    }  
}

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

Some significant characteristics of Generic types 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, which can sometimes result in a great disaster. The following example shows adding an integer, string, and object to the collection of an ArrayList type;

ArrayList obj = new ArrayList();  
obj.Add(50);  
obj.Add("Dog");  
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; 

foreach(int i in obj)  
{  
    Console.WriteLine(i);    
}  

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; 

Test<int> obj = new Test<int>();  
obj.Add(50);  
obj.Add("Dog");            //compiler error  
obj.Add(new TestClass());  //compiler error  

Performance

Another feature of Generics is performance. Using value types with non-generic collection classes results in boxing and unboxing overhead when a value type is converted to a 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 occurs. 

ArrayList  obj = new ArrayList();   
obj.Add(50);    //boxing- convert value type to reference type  
int x= (int)obj[0]; //unboxing  
foreach(int i in obj)  
{  
   Console.WriteLine(i);   // unboxing  
}

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 occur as in the following; 

TestClass<int> obj = new TestClass<int>();  
obj.Add(50);    //No boxing  
int x= obj[0]; // No unboxing  
foreach(int i in obj)  
{  
   Console.WriteLine(i);   //No unboxing  
}

Binary Code reuse

Generics provide a kind of source code protection. A Generic class can be defined once and instantiated with many different types. Generics can be defined in one CLR-supported language and used in another .NET language. The following TestClass<T> is instantiated with an int and string types: 

TestClass<int> obj = new TestClass<int>();  
obj.Add(50);  
  
TestClass<string> obj1 = new TestClass<string>();  
Obj1.Add("hello");    

Generic Methods

While most developers will typically use the existing generic types within the base class libraries, building your own generic members and custom generic types is possible.

This example aims 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 the ref keyword.

using System;  
using System.Collections.Generic;  
  
namespace GenericApp  
{  
    class Program  
    {  
        //Generic method  
        static void Swap<T>(ref T a, ref T b)  
        {  
            T temp;  
            temp = a;  
            a = b;  
            b = temp;  
        }  
        static void Main(string[] args)  
        {  
            // Swap of two integers.  
            int a = 40, b = 60;  
            Console.WriteLine("Before swap: {0}, {1}", a, b);  
  
            Swap<int>(ref a, ref b);  
  
            Console.WriteLine("After swap: {0}, {1}", a, b);  
  
            Console.ReadLine();  
        }  
    }  
} 

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 keys and the values they can store, respectively.

Simple Example of a Dictionary

The following example demonstrates simple dictionary collections using Generics. This program creates a Dictionary-type object that accepts an int as the key and a string as the value. Then, we add some string values to the dictionary collection and display the dictionary collection elements.

using System;  
using System.Collections.Generic;  
  
namespace GenericApp  
{  
    public class Program    
    {  
        static void Main(string[] args)  
        {  
            //define Dictionary collection  
            Dictionary<int,string> dObj = new Dictionary<int,string>(5);  
  
            //add elements to Dictionary  
            dObj.Add(1,1,"Tom");    
            dObj.Add(2,"John");  
            dObj.Add(3, "Maria");  
            dObj.Add(4, "Max");  
            dObj.Add(5, "Ram");
  
            //print data  
            for (int i = 1; i <= dObj.Count;i++)  
            {  
                Console.WriteLine(dObj[i]);  
            }  
            Console.ReadKey();  
        }  
    }  
}

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 employee. 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 are added to the dictionary collection. Finally, the collection elements are iterated using a foreach statement and displayed on the screen.

using System;  
using System.Text;  
using System.Collections.Generic;  
  
namespace GenericApp  
{  
    public class emp   
    {  
        private string name;  
        private int salary;  
   
        public emp(string name,int salary)  
        {  
            this.name = name;  
            this.salary = salary;   
        }  
        public override string ToString()  
        {  
            StringBuilder sb = new StringBuilder(200);  
            sb.AppendFormat("{0},{1}",name,salary);  
  
            return sb.ToString();  
        }  
  
    }  
    public class Program    
    {  
        static void Main(string[] args)  
        {  
            //define Dictionary collection  
            Dictionary<string, emp> dObj = new Dictionary<string, emp>(2);  
  
            //add elements to Dictionary  
            emp tom = new emp("tom", 2000);  
            dObj.Add("tom",tom);   // key,value  
            emp john = new emp("john", 4000);  
            dObj.Add("john",john);  
  
            //print data  
            foreach(Object str in dObj.Values)  
            {  
               Console.WriteLine(str);  
            }  
   
            Console.ReadKey();  
        }  
    }  
}  

Queues

Queues are a special type of container that ensures the items are 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 System.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, and finally displays the entire items into the collection using the while statement.

using System;  
using System.Collections;  
  
namespace GenericApp  
{  
    class Program  
    {  
        static void Main(string[] args)  
        {  
            //Defines a Queue.  
            Queue qObj = new Queue();  
  
            //adding string values into collection  
            qObj.Enqueue("Tom");  
            qObj.Enqueue("Harry");  
            qObj.Enqueue("Maria");       
            qObj.Enqueue("john");  
  
            //displaying collections  
            while(qObj.Count !=0 )  
            {  
                Console.WriteLine(qObj.Dequeue());  
            }  
  
            Console.ReadKey();    
        }  
    }  
} 

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 demonstrates a stack collection. First, an array-type object is referenced into the stack collection. Then, the values of the elements in the collection are removed from the stack using the Pop() method and displayed on the screen.

using System;  
using System.Collections;  
  
namespace GenericApp  
{  
    class Program  
    {  
        static void Main(string[] args)  
        {  
            int[] iArray = new int[] {1,2,3,4,5,6,7,8,9,10 };  
  
            //Define a stack  
            Stack sObj = new Stack(iArray);  
  
            Console.WriteLine("Total items="+sObj.Count);  
  
            //displaying collections  
            for (int i = 0; i < sObj.Count;++i )  
            {  
                Console.WriteLine(sObj.Pop());  
            }  
  
            Console.ReadKey();    
        }  
    }  
}

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:

using System;  
using System.Collections.Generic;  
  
namespace GenericApp  
{  
    public class Program    
    {  
        static void Main(string[] args)  
        {  
            //define Dictionary collection  
            Dictionary<int,string> dObj = new Dictionary<int,string>(5);  
  
           //add elements to Dictionary  
            dObj.Add(1,"Tom");    
            dObj.Add(2,"John");  
            dObj.Add(3, "Maria");  
            dObj.Add(4, "Max");  
            dObj.Add(5, "Ram");  
  
            //print data  
            foreach(string i in dObj.Values)  
            {  
                Console.WriteLine(i);  
            }  
            Console.ReadKey();  
        }  
    }  
}

Further readings

Here are recommended articles on collections: 

  1. C# Dictionary
  2. C# Queue
  3. C# Stack
  4. C# List
  5. C# Arrays
  6. C# HashTable
  7. C# StringCollection
  8. C# ArrayList
  9. C# HashSet
  10. C# LinkedList


Similar Articles