Customize Consent Preferences

We use cookies to help you navigate efficiently and perform certain functions. You will find detailed information about all cookies under each consent category below.

The cookies that are categorized as "Necessary" are stored on your browser as they are essential for enabling the basic functionalities of the site. ... 

Always Active

Necessary cookies are required to enable the basic features of this site, such as providing secure log-in or adjusting your consent preferences. These cookies do not store any personally identifiable data.

No cookies to display.

Functional cookies help perform certain functionalities like sharing the content of the website on social media platforms, collecting feedback, and other third-party features.

No cookies to display.

Analytical cookies are used to understand how visitors interact with the website. These cookies help provide information on metrics such as the number of visitors, bounce rate, traffic source, etc.

No cookies to display.

Performance cookies are used to understand and analyze the key performance indexes of the website which helps in delivering a better user experience for the visitors.

No cookies to display.

Advertisement cookies are used to provide visitors with customized advertisements based on the pages you visited previously and to analyze the effectiveness of the ad campaigns.

No cookies to display.

C#

C# Generics

Generics are introduced in C# 2.0. It is the most powerful features of C#. In C# 1.0, when we design a class that use other types that is not known at defining the structure of the class, then type should be an object type. In the class, we have to typecast the type using boxing and unboxing and then we can use the type.

Generics introduced the concept of type parameters. With the type parameters, we can design a generic class which uses other types with type safety. Generic classes defer the specification of types until the class is instantiated. For e.g. when designing a generic class, we assume this class can work with any type. When we instantiated the class we specify the actual data type which we want to use. For example, we can define int, string, double, or any custom data type. For defining the Generic class, we use <T> after the class name.

Below is the example of Generic class.


public class GenericClass<T>
{
    public void DoWork(T item)
    {
        //work on T item
    }
}

In the above class, we have not defined the actual type of item parameter. On client side, we can declare this Generic class with any type.


GenericClass<int> intClass = new GenericClass<int>();

GenericClass<string> stringClass = new GenericClass<string>();

GenericClass<CustomClass> customClass = new GenericClass<CustomClass>();

In the above example, we have declared GenericClass with int, string, and CustomClass. <T> in the Generic class is Type Parameter. If we instantiated Generic class with <int>, then we can only pass int in the DoWork method.


GenericClass<int> intClass = new GenericClass<int>();
intClass.DoWork(44);        // only pass int parameter
intClass.DoWork("string");  // this statement will not compile as we can pass only ints.

Generics Features

  1. It provides type safety as you can only pass type that is declared at the instantiation time.
  2. Code is reusable and can work with any data type.
  3. We can use Generic with classes, interfaces, events, and delegates.
  4. We can put constraints on generic class, so that client can use only selected types. I’ll this feature later.

.NET provides several generic classes and interfaces. These are exists in System.Collections.Generic namespace. Some are listed below:

  • HashSet<T>
  • LinkedList<T>
  • List<T>
  • Queue<T>
  • Stack<T>
  • ICollection<T>
  • IComparer<T>
  • IEnumerable<T>
  • IEnumerator<T>
  • ILIst<T>

Example of using List<T> class:


public class NodeList<T>
{
    private List<T> nodes;

    public NodeList()
    {
        this.nodes = new List<T>();
    }

    public void AddNode(T newNode)
    {
        nodes.Add(newNode);
    }

    public void DeleteNode(T nodeToRemove)
    {
        nodes.Remove(nodeToRemove);
    }

    public void ProcessAllNodes()
    {
        foreach(var node in nodes)
        {
            Console.WriteLine(node.ToString());
        }
    }
}

class Program
{
    static void Main(string[] args)
    {
        NodeList<int> nodesOfInt = new NodeList<int>();
        nodesOfInt.AddNode(2);
        nodesOfInt.AddNode(4);
        nodesOfInt.AddNode(6);
        nodesOfInt.AddNode(7);
        nodesOfInt.DeleteNode(7);

        nodesOfInt.ProcessAllNodes();

        NodeList<string> nodesOfString = new NodeList<string>();
        nodesOfString.AddNode("Lory");
        nodesOfString.AddNode("Julia");
        nodesOfString.AddNode("Lerman");
        nodesOfString.AddNode("James");

        nodesOfString.ProcessAllNodes();

        /// Result
        /// 2
        /// 4
        /// 6
        /// Lory
        /// Julia
        /// Lerman
        /// James
    }
}

Generic Constraints

Constraints are validations that we can put on generic Type parameter. At the instantiation time of generic class, if client provides invalid type parameter then compile will give an error.

There are six types of constraints.

  1. where T : struct  – Type argument must be a value type
  2. where T : class – Type argument must be a reference type
  3. where T : new() – Type argument must have a public parameterless constructor.
  4. where T : <base class> – Type argument must inherit from <base class> class.
  5. where T : <interface> –  Type argument must implement from <interface> interface.
  6. where T : U – There are two type arguments T and U. T must be inherit from U.

Constraints examples:

where T: struct example

In the struct constraint, we can only specify a value type in the type argument. Some value types are int, double, decimal, and DateTime.


public class NodeList<T> where T : struct
{
}

NodeList<int> lst = new NodeList<int>();    //No error, as int is a value type
NodeList<string> lst1 = new NodeList<string>(); //Error as string is a reference type
NodeList<Employee> lst2 = new NodeList<Employee>(); //Error as Employee is a reference type;

where T : class example

 

In the class constraint, we can only specify reference type in the type argument. Some reference type are string, class, and delegates.


public class NodeList<T> where T : class
{
}

NodeList<string> nodesOfString = new NodeList<string>();        // string is a reference type
NodeList<Employee> nodesOfEmployee = new NodeList<Employee>();  // Employee is a reference type
NodeList<EventHandler> nodesOfAction = new NodeList<EventHandler>();    //EventHandler is a delegate and a reference type

where T : new() example

In the new() constraint, we can only specify types which has parameterless constructor like shown below:


public class NodeList<T> where T : new()
{
}

    class Program
    {
        static void Main(string[] args)
        {
NodeList<Employee> employeeNodes = new NodeList<Employee>(); //No Error, as Emplyoee has constructor of no parameters.

NodeList<Customer> customerNodes = new NodeList<Customer>(); //Error, as Customer has constructor which takes parameters.
            
        }
    }

public class Employee
{
    public Employee()
    {

    }
}

public class Customer
{
    public Customer(string customerName)
    {

    }
}

where T : <base class> example

In the <base class> constraint, we can only specify types that in inherit from <base class> like shown below:


public class NodeList<T> where T : BaseEmployee
{
}
public class BaseEmployee
{

}

public class Employee : BaseEmployee
{
}

public class Customer
{
}

class Program
{
    static void Main(string[] args)
    {
        NodeList<Employee> employeeNodes = new NodeList<Employee>(); //No Error, as Emplyoee is inherited from BaseEmployee

        NodeList<Customer> customerNodes = new NodeList<Customer>(); //Error, as Customer is not inheried from BaseEmployee

    }
}

where T : <interface> example

In the <interface> constraints, we can only specify types that implements the <interface> like shown below:


public class NodeList<T> where T : IEmployee
{
}

public interface IEmployee
{
}

public class Employee : IEmployee
{
}

public class Customer
{
}

class Program
{
    static void Main(string[] args)
    {
        NodeList<Employee> employeeNodes = new NodeList<Employee>(); //No Error, as Emplyoee implements the IEmployee interface

        NodeList<Customer> customerNodes = new NodeList<Customer>(); //Error, as Customer does not implements the IEmployee interface

    }
}

where T : U example

In this constraint, there are two type arguments T and U. U can be a interface, abstract class, or simple class. T must inherit or implements the U class like shown below:


public class NodeList<T, U> where T : U
{
    public void DoWork(T subClass, U baseClass)
    {

    }
}

public interface IEmployee
{
}

public class Employee : IEmployee
{
}

class Program
{
    static void Main(string[] args)
    {
        NodeList<Employee, IEmployee> employeeNodes = new NodeList<Employee, IEmployee>();
    }
}

Final Words

C# Generics classes are powerful way of creating the types that use other types. Generic classes make classes reusable and with type-safety.