What are generics in C#?

C# Type System Basic Fresher1–2 Yrs Conceptual 2 min read

Quick Answer

Generics let you write a class, method, interface or delegate with a placeholder type parameter (T) that the caller fills in. The same code works for any type, with full compile-time type safety and no boxing — List<int> and List<string> share one implementation but are strongly typed.

Generics let me write code once with a type placeholder — T — and the caller decides the actual type. So List<T> is one implementation but List<int> and List<Customer> are both fully type-checked at compile time, and value types aren't boxed. That's basically why they were added — the old non-generic collections stored object, so you got casts and boxing everywhere. I can also add constraints, like where T : IComparable<T>, so I can actually call methods on T inside the generic.

Before generics you either wrote a type-specific class for every type, or used object and paid for casts and boxing (the old ArrayList). Generics give you one implementation that the compiler specialises.

  • Type safety — List<int> won't accept a string; the error is at compile time.
  • Performance — value types are stored directly, no boxing; the JIT shares one native implementation for all reference-type arguments and a specialised one per value type.
  • Constraints restrict T: where T : class, where T : struct, where T : new(), where T : IComparable<T>, where T : BaseClass. Constraints let you call members on T inside the generic.
  • Generic methods can infer T from arguments: Swap(ref a, ref b).

Generics power the whole collection library, LINQ, Task<T>, Nullable<T> and dependency injection (ILogger<T>).

C# — generic class + constraint
public class Repository<T> where T : class, IEntity
{
    private readonly List<T> _items = new();

    public T? Find(int id) => _items.FirstOrDefault(x => x.Id == id);
    public void Add(T item) => _items.Add(item);
}

// generic method with type inference
static T Max<T>(T a, T b) where T : IComparable<T>
    => a.CompareTo(b) >= 0 ? a : b;

  • One implementation, many types — no copy-paste type-specific classes
  • Compile-time type safety instead of runtime cast errors
  • No boxing for value types in generic collections
  • Constraints let the generic code use members of T safely