Generics in .NET

Generics introduce the concept of parameterized types to the .NET Framework. This allows you to design classes, interfaces, methods, and delegates that operate on types specified by a type parameter. Generics provide a way to create reusable, type-safe components without sacrificing type safety or performance.

Why Use Generics?

Before generics, using collections like ArrayList required casting elements to their specific types, which could lead to runtime errors if the cast was incorrect. Generics address this by providing compile-time type checking.

Benefits of Generics:

Generic Classes

A generic class is defined with a type parameter in angle brackets (<T>). The type parameter T acts as a placeholder for a type that will be specified when an instance of the class is created.

Example: A Generic Stack


using System;
using System.Collections.Generic;

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

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

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

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

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

You can then use this generic Stack class with specific types:


// Stack of integers
Stack<int> intStack = new Stack<int>();
intStack.Push(10);
intStack.Push(20);
int x = intStack.Pop(); // x will be 20

// Stack of strings
Stack<string> stringStack = new Stack<string>();
stringStack.Push("Hello");
stringStack.Push("World");
string s = stringStack.Pop(); // s will be "World"
            

Generic Methods

Generic methods are methods that have type parameters. They can be defined within generic or non-generic classes.

Example: A Generic Swap Method


using System;

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

Usage:


int num1 = 5;
int num2 = 10;
Utilities.Swap(ref num1, ref num2); // num1 is now 10, num2 is now 5

string str1 = "A";
string str2 = "B";
Utilities.Swap(ref str1, ref str2); // str1 is now "B", str2 is now "A"
            

Type Constraints

You can specify constraints on the type parameters used in generic types and methods. Constraints limit the types that can be used as arguments for the type parameter, allowing you to call methods defined on the constrained type or to check for specific base types.

Common Constraints:

Example: Constraint on a Generic Class


public class DataProcessor<T> where T : IComparable<T>
{
    public T FindMax(T item1, T item2)
    {
        // T must implement IComparable<T> to use CompareTo
        if (item1.CompareTo(item2) > 0)
        {
            return item1;
        }
        return item2;
    }
}
            

Generic Interfaces

Generic interfaces allow you to define interfaces that can be implemented by generic classes.

Example: Generic List Interface

The .NET Framework's System.Collections.Generic namespace provides many powerful generic interfaces and classes, such as IList<T>, IEnumerable<T>, Dictionary<TKey, TValue>, and more.

Key Takeaway: Generics enhance type safety and performance in .NET applications by allowing you to create flexible and reusable code components that operate on specific types without sacrificing compile-time checks.

Further Reading

MSDN Library: Generics
https://docs.microsoft.com/en-us/dotnet/standard/generics