.NET Generics Documentation

Introduction to Generics

Generics enable you to define classes, interfaces, and methods with a placeholder for the type of data they store or manipulate. This provides type safety without sacrificing performance.

Why use Generics?

Basic Syntax

public class List<T> : IList<T>
{
    private T[] _items;
    public void Add(T item) { /*...*/ }
    public T this[int index] => _items[index];
}

Common Generic Types

Generic Type Definition

A generic type can declare one or more type parameters. These are listed in angle brackets after the type name.

public class Cache<TKey,TValue> where TKey : notnull
{
    private readonly Dictionary<TKey,TValue> _store = new();
    public void Set(TKey key, TValue value) => _store[key] = value;
    public TValue Get(TKey key) => _store[key];
}

Generic Constraints

Constraints restrict the types that can be used as arguments for a generic parameter.

public class Repository<TEntity>
    where TEntity : class, IEntity, new()
{
    public TEntity Create() => new TEntity();
}

Read more about type constraints.

Variance

Variance allows you to use a more derived or less derived type than originally specified for generic interfaces and delegates.

// Covariant interface (out)
public interface IProducer<out T>
{
    T Produce();
}

// Contravariant interface (in)
public interface IConsumer<in T>
{
    void Consume(T item);
}

Read more about generic variance.

Best Practices