C# Generics

Generics in C# provide a way to write type-safe as the compiler can perform compile-time checks on code for type safety and it also makes code reusable and faster than using objects as it either avoids boxing/unboxing.

Commonly we create generic classes, interfaces, methods and delegates.

The most commonly used generic classes are the collection classes derived from the namespace "System.Collections.Generic". This namespace consists of many generic classes, and structs such as List<T>, Stack<T>, Queue<T>, Dictionary<TKey, TValue>.

Below is an example of a generic class which is a generic repository that accepts an object and adds it into a list.

public class GenericList<T> //T is called generic type parameter
{
    private readonly List<T> _items = new();

    public void Add(T item)
    {
        _items.Add(item)
    }

    public void Save()
    {
        foreach(var item in _items)
        {
            //save items
        }
    }
}

Note:

  • We can inherit the generic class.

  • We can define multiple type parameters for the generic class.

Type Constraints

Type constraints ensure that the generic code can only work with the mentioned specific type. This helps in making the code more robust as it reduces runtime errors.

Type constraints also provide a way to define any common properties in a base class that can be used within the class. A simple example is shown below.

public interface IProduct
{
    public int Id { get; set; }
}

public class GenericProductList<T> : IProduct
{
    private readonly List<T> _items = new();

    public T GetProductById(int id)
    {
        //implementation
        return _items.Find(item => item.Id == id);
        //return null; //returning null results in compilation 
                  //error.
                  //If generic class implements a reference type
                  //then we can return null                            
    }
}

Using new() constraint

With the new() constraint we can call the public parameterless constructor of the type parameter. When used together with other constraints, the new() constraint must be specified last. The new() constraint can't be combined with the struct and unmanaged constraints.

.NET also supports few built-in generic interfaces such as IList<T>, IEnumerable<T>, IEnumerator<T>.

Did you find this article valuable?

Support Sankarshan Ramesh by becoming a sponsor. Any amount is appreciated!