C# Generics Tutorial For Beginners With Examples

Generics in C#.NET allows a Class, Method, Interface or Delegate to be created without the need of using a specific data type within them.
The assignment of the actual data type is done during the declaration and instantiation of a class, interface and delegate or during the invocation of a method.

Important Features of Generics

Below some of the important features of C# Generics has been provided.
  • Performance wise a generic type is more efficient.
  • It guarantees type safety.
  • It helps in code reusability.
  • It is commonly used in the collection classes.
  • .NET framework uses System.collections.generic namespace for all the generic collection classes.
  • It allows programmers to create their own generic classes, methods, interfaces, delegate and events.
  • Type information about types used in a generic data type is only available in runtime. Reflection can be used to get this information.

Generic Data Structures

In this tutorial we will discuss the below Generic data structures:
  • Generic Classes
  • Generic Interfaces
  • Generic Methods
  • Generic Delegates
If you want to understand ClassInterfacesMethods and Delegates then please go through the respective tutorials first.

Generic Classes

If a class uses a variable of type string to store some text and if this class is defined as a C# Generic Class then instead of the data type string an alphabet or a any other word can be used. During the declaration and instantiation of this class the intended data type should be used.

Example of Generic Class

Go through the below example to understand how Generics in C# programming is used. Below the class NameMe is a Generic class with a type parameter 'T' and method named asAssignName. This method accepts any data of type 'T' and also returns data of type 'T'.
In this example the actual data type is specified while instantiating the class NameMe in theMain method.
    namespace GenericsNamespace
    {
        public class NameMe<T>
        {
            T myName;

            public T AssignName(T name)
            {
                myName = name;
                return myName;
            }
        }

        class GenericsExample
        {
            static void Main(string[] args)
            {
                NameMe<string> nameClass = new NameMe<string>();

                string myName = nameClass.AssignName("csharp Tutorial");
                Console.WriteLine("My name is: " + myName);
                Console.ReadLine();
            }
        }    
    }
        
Output: csharp Tutorial
In a Generic data structure instead of the letter 'T' any letter can be used in either upper or lower case. Similarly, instead of letter, words can also be used.

Why Generics?

Above we have seen how a Generic class works but an obvious question that can be asked here is why to use a Generic type when we can achieve the same results with a non-generic type.
Lets understand this with an example where we compare a non-Generic Arraylist with a Generic List.
In the below example an ArrayList called as list was created and some numbers were added to it. After that the second element of this ArrayList was displayed to the console.
    static void Main(string[] args)
    {
        ArrayList list = new ArrayList();
        list.Add(1);
        list.Add(2);

        int numberone = (int)list[0];
        int numbertwo = (int)list[1];

        Console.WriteLine(numberone);
        Console.WriteLine(numbertwo);
        Console.ReadLine();
    }
        
Output: 
1
2
The above example works fine for the scenario where only numbers are added. However, when the requirement changes to add few strings to this ArrayList along with numbers then will it work? The below example does exactly the same but when it is executed it throws anInvalidCastException.
    static void Main(string[] args)
    {
        ArrayList list = new ArrayList();
        list.Add(1);
        list.Add("Hello");

        int numberone = (int)list[0];
        int numbertwo = (int)list[1];

        Console.WriteLine(numberone);
        Console.WriteLine(numbertwo);
        Console.ReadLine();
    }
        

Type safety through Generics

Now lets repeat the same example below by using a Generic List collection class. Once Generic List collection class is used the compiler started complaining. So before running the code it is known that this code will not work. This is why Generic types are considered type safe as it will never throw exception related to types during runtime.
    static void Main(string[] args)
    {
        List<int> list = new List<int>();
        list.Add(1);
        list.Add("Hello");

        int numberone = list[0];
        int numbertwo = list[1];

        Console.WriteLine(numberone);
        Console.WriteLine(numbertwo);
        Console.ReadLine();
    }
        

Better performance through Generics

It is also important to note that when non generic type like ArrayList is used, it considers every element as an object. So when a value type like integer is assigned to it, the Arraylist moves it from stack memory to heap memory (Boxing). Similarly, when an element of integer type is retrieved from the ArrayList it has to be cast to the integer value type. So it has to be physically moved from Heap memory to Stack memory(Unboxing). This affects the performance of the application.

Generic Interfaces

Like class, Generic interfaces can also be created. These type of interfaces help to define members that should be exposed by a generic class.

Creating a generic Interface

A generic Interface can be created as shown in the below example. It has members like a non-generic interface.
In the below example the interface defines two methods which do not return any value but accepts parameters of a generic type.
    public interface IStatistics<T>
    {
        void AddHeight(T height);
        void RemoveHeight(T weight);
    }
        

Implementing a generic Interface

Generic Interface is implemented just like any other interface. All the members of this interface should be implemented in the class using this interface. The class that is implementing the Generic interface can have additional members if required. It can also override the members defined in the Generic interface.
    public class Statistics<T> : IStatistics<T>
    {
        private ArrayList list;

        public Statistics()
        {
            list = new ArrayList();
        }

        public void AddHeight(T height)
        {
            list.Add(height);
        }

        public void RemoveHeight(T height)
        {
            list.Remove(height);
        }
    }        
        

Features of a Generic Interface

  • A Generic interface can have more than one parameter. It is perfectly legal to have an interface as shown below:
       interface  IStatistics<T, W>{ }
                
  • Multiple Generic interfaces can be implemented by a Generic class.
        public class Statistics<T> : IStatistics<T>, ITutorial<T>
        {
    
        }
                
  • A C# Generic interface can inherit from a non-generic interface. In this case the class that is implementing the Generic interface has to implement all the methods defined in the generic and non-generic Interfaces.
        public interface ITutorial
        {
            string ReadBook(string data);
        }
    
        public interface ITutorial<T> : ITutorial
        {
            void ReadData(T data);
        }
    
        public class Statistics<T> : ITutorial<T>
        {
            public void ReadData(T data)
            {
                //ReadData code
            }
    
            public string ReadBook(string data)
            {
                //ReadBook code
            }
         }
                    

Generic Methods

Like Generic class and interface, Generic methods can also be used in C#. These methods can have one or more parameters which can be defined as Generic.
Every C# Generic method should have a at least one type parameter similar to <T>.

Example of Generic Methods

Below example shows the syntax of a Generic method. Here, the type parameters are <W, B>.
        
    public void AddHeight<W, B>(W height, B lowestHeight, string name)
    {
        list.Add(name);
        list.Add(height);
        list.Add(lowestHeight);
    }
        
While creating a Generic method in C# it is a good practice not to use the same type parameter used by the Generic class with in which this method is present.
A situation like below should be avoided as the type parameter 'U' of the method hides the type parameter 'U' of the class. This generates compiler warning CS0693.
    public class Statistics<U>
    {
        public void Show<U>()
        {
            //Code
        }
    }            
                    
A more detailed example of how a Generic method is used in the real world is shown below:
    public class Statistics
    {
        private ArrayList list;

        public Statistics()
        {
            list = new ArrayList();
        }

        public void AddHeight<W, B>(W height, B lowestHeight, string name)
        {
            list.Add(name);
            list.Add(height);
            list.Add(lowestHeight);
        }

        public int Count
        {
            get
            {
                return list.Count;
            }                
        }

    }

    static void Main(string[] args)
    {
        Statistics statistics = new Statistics();
        statistics.AddHeight<int, int>(23, 12, "Tree");

        Console.WriteLine(statistics.Count);
        Console.ReadLine();
    }                    
        
Output: 3

Generic Delegates

Like Generic Methods and Class, Delegates can also define their own type parameters. These type parameters can be defined while instantiating a delegate object. These type of Delegate objects are called as Generic Delegates.

Example of Generic Delegates

The example discussed below uses a Delegate type Sort with a type parameter U. Except the parameter type the signature of method SortNumbers matches this Generic Delegate type.
Hence, To hold the reference of method SortNumbers a delegate object sort was declared using the data type int.
    class GenericsExample
    {
        //Delegate Type
       public delegate void Sort<U>(U item);
        
        //Method whose signature is matching the delegate type
        public static void SortNumbers(int number)
        {
            //Method code
        }

        static void Main(string[] args)
        {
            //Delegate object that holds the reference to the SortNumbers method.
            Sort<int> sort = SortNumbers;       
            Console.WriteLine("Generic delegate object created");
            Console.ReadLine();
        }        
    }        
        
Output:
Generic delegate object created