C# Story

Chapter #7 – Generics

generic types, methods, constraints, variance

7.0 Prologue

Generics let you write type-safe, reusable code without duplicating logic for every data type you need to support. They eliminate boxing overhead for value types and catch type mismatches at compile time.

7.1 Generic Classes

A generic class is parameterized by one or more type arguments in angle brackets. The type argument is substituted at use-site: public class Stack<T> { private readonly List<T> _items = new(); public void Push(T item) => _items.Add(item); public T Pop() { if (_items.Count == 0) throw new InvalidOperationException("empty"); T top = _items[^1]; _items.RemoveAt(_items.Count - 1); return top; } public int Count => _items.Count; } var s = new Stack<int>(); s.Push(1); s.Push(2); int top = s.Pop(); // 2 Multiple type parameters are allowed: class Pair<TFirst, TSecond>.

7.2 Generic Methods

A method may have its own type parameters independent of its enclosing class: public static T Max<T>(T a, T b) where T : IComparable<T> => a.CompareTo(b) >= 0 ? a : b; int bigger = Max(3, 7); // T inferred as int string later = Max("apple", "banana"); // T inferred as string The compiler infers type arguments from method arguments in most cases, so you rarely need to supply them explicitly: Max(3, 7) rather than Max<int>(3, 7).

7.3 Constraints

Constraints narrow the set of types that can be supplied for a type parameter. They appear after the where keyword:
Constraint Meaning
where T : classT must be a reference type
where T : structT must be a non-nullable value type
where T : new()T must have a public parameterless constructor
where T : SomeBaseClassT must derive from SomeBaseClass
where T : ISomeInterfaceT must implement ISomeInterface
where T : notnullT must be a non-nullable type (C# 8+)
where T : unmanagedT must be an unmanaged type (C# 7.3+)
Multiple constraints may be combined: where T : class, IComparable<T>, new() Static abstract members in interfaces (C# 11) enable constraints that require static operators, enabling generic math: where T : INumber<T>

7.4 Variance

Generic interfaces and delegate types may declare variance on their type parameters to allow safe assignment between instantiations:
  • Covariance (out T): used for producer/output positions. If Dog derives from Animal, an IEnumerable<Dog> can be assigned to an IEnumerable<Animal>.
  • Contravariance (in T): used for consumer/input positions. An IComparer<Animal> can be used where an IComparer<Dog> is needed.
// IEnumerable<out T> is covariant IEnumerable<Dog> dogs = new List<Dog> { new Dog("Rex") }; IEnumerable<Animal> animals = dogs; // valid — covariant // Action<in T> is contravariant Action<Animal> feedAnimal = a => Console.WriteLine($"feeding {a.Name}"); Action<Dog> feedDog = feedAnimal; // valid — contravariant

7.5 Generic Types in the BCL

The .NET Base Class Library is built on generics. Commonly used generic types include:
  • List<T>, LinkedList<T>, Queue<T>, Stack<T>, HashSet<T>, SortedSet<T>
  • Dictionary<TKey, TValue>, SortedDictionary<TKey, TValue>
  • IEnumerable<T>, ICollection<T>, IList<T>, IReadOnlyList<T>
  • Nullable<T>, Task<T>, Lazy<T>, Memory<T>, Span<T>
  • Func<…>, Action<…>, Predicate<T>, Comparison<T>

7.6 C# Generics vs C++ Templates

Aspect C# Generics C++ Templates
Compilation Compiled once to IL; JIT specializes for value types Each instantiation compiled separately (code bloat possible)
Constraints Explicit where clauses; checked at compile time Duck typing; errors may be cryptic; concepts (C++20) improve this
Specialization Not directly supported; achieved via method overloads or interfaces Full / partial specialization supported
Reflection Generic type information available at runtime No runtime type info for templates
Value types Specialized native code per value type; no boxing Inlined at each instantiation; very efficient

7.7 Epilogue

This chapter covered generic classes, generic methods, constraints, variance, and how C# generics compare with C++ templates. The next chapter surveys the .NET Base Class Library.

7.8 References

Generics — Microsoft docs
Covariance & contravariance
Generic collections in .NET