MSDN Documentation

Microsoft Developer Network

C# Advanced Generics

Generics in C# provide a way to define type-safe collections and methods that can operate on any data type without sacrificing type safety or performance. This document explores advanced concepts and practical applications of generics.

Introduction to Generics

Before generics, collections like `ArrayList` stored elements as objects, requiring casting and leading to potential runtime errors and performance overhead. Generics solve this by allowing you to specify the type of elements a collection or method can work with.

Key Benefit: Type safety and improved performance by avoiding boxing/unboxing.

Generic Classes

You can define your own generic classes. These classes have type parameters that are specified when an instance of the class is created.

public class Stack<T>
{
    private List<T> elements = new List<T>();

    public void Push(T item)
    {
        elements.Add(item);
    }

    public T Pop()
    {
        if (elements.Count == 0)
        {
            throw new InvalidOperationException("Stack is empty.");
        }
        T item = elements[elements.Count - 1];
        elements.RemoveAt(elements.Count - 1);
        return item;
    }
}

Usage:

Stack<int> intStack = new Stack<int>();
intStack.Push(10);
int num = intStack.Pop(); // num is int, no cast needed

Stack<string> stringStack = new Stack<string>();
stringStack.Push("Hello");
string str = stringStack.Pop(); // str is string

Generic Interfaces

Similar to classes, interfaces can also be generic. This is common for defining contracts for generic types.

public interface IRepository<TEntity> where TEntity : class
{
    TEntity GetById(int id);
    void Add(TEntity entity);
    void Update(TEntity entity);
    void Delete(int id);
}

Implementation:

public class UserRepository : IRepository<User>
{
    // ... implementation details ...
}

Generic Methods

Methods can also be generic, independent of their containing class (if any).

public static class Utility
{
    public static void Swap<T>(ref T a, ref T b)
    {
        T temp = a;
        a = b;
        b = temp;
    }
}

Usage:

int x = 5, y = 10;
Utility.Swap<int>(ref x, ref y); // Explicitly specify type

string s1 = "apple", s2 = "banana";
Utility.Swap(ref s1, ref s2); // Type inference can often be used

Constraints on Generic Types

Constraints allow you to restrict the types that can be used as type arguments for a generic type parameter. This enables you to call methods or access properties specific to certain types.

public class Factory<TProduct> where TProduct : new()
{
    public TProduct CreateInstance()
    {
        return new TProduct(); // Possible because of the new() constraint
    }
}

Covariance and Contravariance

These advanced features allow for more flexible use of generic types, particularly with interfaces and delegates.

// Covariance example
IEnumerable<string> strings = new List<string> { "a", "b" };
IEnumerable<object> objects = strings; // Covariant assignment

// Contravariance example
Action<object> printObject = obj => Console.WriteLine(obj);
Action<string> printString = printObject; // Contravariant assignment
printString("Hello"); // Outputs "Hello" to the console

Generic Collections

The .NET Framework provides a rich set of generic collection types in the System.Collections.Generic namespace:

Using these avoids the overhead and type-safety issues of older non-generic collections.

Common Use Cases

Best Practice: Always use generic collections over non-generic ones when possible for type safety and performance.