.NETMedium
C# generic constraints — when and how each "where T :" actually works
Constraints narrow what T can be so the compiler lets you call methods or constructors on it.
| Constraint | Meaning |
|---|---|
where T : struct | Non-nullable value type |
where T : class | Reference type |
where T : class? | Reference type, may be null (C# 8+) |
where T : notnull | Non-nullable (value or ref) |
where T : unmanaged | Pure value type, no reference fields |
where T : new() | Public parameterless constructor |
where T : BaseClass | T inherits from BaseClass |
where T : ISomething | T implements interface |
where T : U | T derives from another type parameter |
where T : enum (C# 7.3+) | T is an enum |
where T : delegate | T is a delegate |
Example combining several
public T CreateDefault<T>() where T : class, IInitializable, new()
{
var x = new T();
x.Initialize();
return x;
}
Why constraints matter
Without them you can only call methods inherited from System.Object. With them, the compiler enables full IntelliSense and the JIT can generate specialized code per value-type T (avoiding boxing).
Common interview pitfalls
where T : structandwhere T : Enumtogether fail —Enumis a class. Use the C# 7.3where T : struct, Enumfor enum-only constraints.- Constraints are part of the method signature for overload resolution but not part of the type identity — two methods differing only in constraints will not compile.
new()constraint forces a runtime check viaActivator.CreateInstancehistorically; modern JIT inlines it for sealed types.
Real codebase pattern
public static TConfig Bind<TConfig>(IConfiguration cfg)
where TConfig : class, new()
{
var instance = new TConfig();
cfg.Bind(instance);
return instance;
}