Generic Constraints
Vaibhav • September 11, 2025
In the previous article, we explored how generic methods allow us to write reusable logic that works across many types. But sometimes, you don’t want to allow just any type. You may want to restrict the types that can be used with your generic class or method-for example, only allowing types that implement a specific interface, or only allowing reference types. That’s where generic constraints come in.
Why Constraints Matter
Generics are powerful because they’re flexible. But that flexibility can lead to problems if you assume certain
capabilities that the type might not have. For example, if you want to call a method like ToString()
or access a property like Name
, you
need to be sure the type supports it. Constraints let you enforce those expectations at compile time.
// Without constraints
public static void PrintName<T>(T item)
{
Console.WriteLine(item.Name); // ❌ Compile error: T might not have a Name property
}
The compiler doesn’t know what T
is, so it can’t guarantee that Name
exists. You need a constraint to fix that.
Using Interface Constraints
The most common constraint is requiring that a type implements a specific interface. This allows you to call methods and access members defined in that interface.
public interface IHasName
{
string Name { get; }
}
public static void PrintName<T>(T item) where T : IHasName
{
Console.WriteLine(item.Name); // ✅ Safe: T must implement IHasName
}
Now the compiler knows that item
has a Name
property, because T
is constrained to types that implement IHasName
.
You can constrain a type to multiple interfaces by separating them with commas: where T : IHasName, IComparable
.
Constraining to Base Classes
You can also constrain a type to inherit from a specific base class. This is useful when you want to work with a known hierarchy.
public class Animal
{
public void Speak() => Console.WriteLine("Animal sound");
}
public static void MakeSound<T>(T creature) where T : Animal
{
creature.Speak(); // ✅ Safe: T must be an Animal
}
This method works with any type that inherits from Animal
, including subclasses
like Dog
or Cat
.
Constraining to Reference or Value Types
Sometimes you want to restrict a generic to only reference types or only value types. C# provides two special constraints for this:
// Reference type constraint
public void SaveObject<T>(T obj) where T : class
{
// T must be a reference type
}
// Value type constraint
public void SaveValue<T>(T value) where T : struct
{
// T must be a value type
}
These constraints help you avoid issues like nullability or boxing. For example, you might want to ensure that a type can be null (reference type) or that it’s stored efficiently (value type).
You cannot use both class
and struct
constraints together-they are mutually exclusive.
Constraining to Types with a Parameterless Constructor
Sometimes you need to create an instance of a generic type using new
. But not
all types have a parameterless constructor. To enforce that, use the new()
constraint.
public static T CreateInstance<T>() where T : new()
{
return new T(); // ✅ Safe: T must have a public parameterless constructor
}
This is useful for factories, builders, or any method that needs to instantiate a generic type.
You can combine new()
with other constraints, but it must
come last: where T : class, new()
.
Combining Multiple Constraints
You can apply multiple constraints to a single type parameter. This gives you fine-grained control over what types are allowed.
public static void Process<T>(T item)
where T : class, IHasName, new()
{
Console.WriteLine(item.Name);
var copy = new T(); // ✅ Safe: T is a reference type, implements IHasName, and has a parameterless constructor
}
This method ensures that T
is a reference type, implements IHasName
, and can be instantiated with new
.
Constraints in Generic Classes
Constraints aren’t limited to methods-you can also apply them to generic classes. This ensures that all methods and properties within the class can safely assume the constraint.
public class Repository<T> where T : IEntity
{
public void Save(T entity)
{
Console.WriteLine($"Saving entity with ID: {entity.Id}");
}
}
Here, T
must implement IEntity
, so the class
can safely access Id
or other members defined in that interface.
Constraints and Inheritance
When a generic class has constraints, those constraints apply to all derived classes unless overridden. You can also add additional constraints in derived classes.
public class BaseProcessor<T> where T : class
{
public virtual void Process(T item) { }
}
public class AdvancedProcessor<T> : BaseProcessor<T> where T : class, IProcessable
{
public override void Process(T item)
{
item.Execute(); // ✅ Safe: T must implement IProcessable
}
}
This pattern lets you build layered abstractions with increasing specificity.
Constraints and LINQ
Constraints are especially useful when working with LINQ. For example, if you want to sort a list of generic
items, you can constrain them to IComparable
:
public static T FindMax<T>(List<T> items) where T : IComparable<T>
{
return items.Max(); // ✅ Safe: T must be comparable
}
Without the constraint, Max()
would fail because the compiler wouldn’t know how
to compare the items.
Common Mistakes with Constraints
Constraints are powerful, but they can be misused. Here are some common mistakes to avoid:
- Forgetting to add a constraint when calling methods or accessing properties.
- Using incompatible constraints (e.g.,
class
andstruct
together). - Assuming a type has a constructor without using
new()
. - Constraining to interfaces that aren’t implemented by the intended types.
Always think about what capabilities your generic type needs. Use constraints to enforce those capabilities early-before bugs sneak in.
Summary
Generic constraints let you restrict the types that can be used with your generic classes and methods. They
ensure type safety, enable specific operations, and improve code clarity. You’ve learned how to use constraints
with interfaces, base classes, reference/value types, and constructors. You’ve also seen how to combine
constraints and apply them in real-world scenarios like LINQ and inheritance. In the next article, we’ll dive
into Generic Collections Deep Dive-exploring how collections like List<T>
, Dictionary<K,V>
, and HashSet<T>
use generics under the hood to deliver performance and
flexibility.