Glossary / Generic constraints
Generic constraints
Generic constraints restrict which types can be used for a type parameter and tell the compiler which operations are valid for that parameter. They express requirements such as reference type, value type, non-null, constructor availability, base class, or implemented interfaces.
Constraint forms
Common constraints include where T : class, struct, unmanaged, notnull, new(), a base class, one or more interfaces, and default in override/interface contexts. class? and nullable-aware forms express nullability intent. new() must generally appear last among ordinary constraints and requires a public parameterless constructor. unmanaged means the type contains no managed references and supports certain low-level interop scenarios.
static T Create<T>() where T : new() => new T();
static void Save<T>(T value) where T : ISerializable { value.Save(); }
Constraints are compile-time restrictions; they do not validate dynamically loaded or deserialized data. A notnull constraint produces diagnostics but does not change runtime representation. C# 11 permits static abstract interface members as generic contracts, enabling operations such as generic numeric algorithms with constrained static dispatch.
Design guidance
Constrain only what implementation requires, not every type property imaginable. Prefer interfaces for capabilities and avoid new() when factories or dependency injection provide stronger control. Test constraints from consumer calls, especially nullable and variance cases.
Interview questions
- What does a constraint enable? It limits type arguments and permits compile-time access to required members.
- What does
unmanagedmean? The type contains no managed references under C# rules. - Does
notnullenforce runtime non-null? No; it gives compile-time diagnostics. - Why avoid
new()constraints in domain APIs? They force a public parameterless construction model and limit invariant control. - What do static abstract interface members enable? Generic algorithms can invoke static operations through type parameters constrained by an interface.