C# Generics: Constraints and Why They Beat object
Codevity Admin · 10/25/2026
Codevity Admin · 10/25/2026
Generics let you write a class or method once and use it with many types while keeping full type safety. , and are all generic types.
Before generics, a reusable collection stored . That has two problems:
A generic fixes both: the compiler rejects the wrong type, and the values are stored directly, with no boxing and no casts.
The compiler infers for from the arguments, so you rarely write yourself.
A constraint tells the compiler what a type parameter must support, which lets you use those members inside the generic code.
| Constraint | Meaning |
|---|---|
| is a reference type | |
| is a non-nullable value type | |
| has a public parameterless constructor, so works | |
| is or derives from it | |
| implements that interface | |
| cannot be a nullable type |
is covariant: because you can only read items out of it, an can be used where an is expected. is not covariant, because you could add an to what is secretly a list of strings.
Variance is a "know the idea" topic for freshers: means the type is only produced (returned), means it is only consumed (taken as input), as in .
List<T>Dictionary<TKey, TValue>Task<T>objectArrayListInvalidCastExceptionintArrayListList<int>TMax(3, 7)Max<int>where T : classTwhere T : structTwhere T : new()Tnew T()where T : SomeBaseTSomeBasewhere T : IComparable<T>Twhere T : notnullTIEnumerable<out T>IEnumerable<string>IEnumerable<object>List<T>intoutinAction<in T>Tnew()List<object>List<T>List<object>List<T>Swap<T>(ref T a, ref T b)Repository<T>AddGetAllwhere T : classList<string>List<object>Console.WriteLine(Max(3, 7)); // 7
Console.WriteLine(Max("apple", "pear")); // pear
var stack = new SimpleStack<string>();
stack.Push("a");
stack.Push("b");
Console.WriteLine(stack.Pop()); // b
static T Max<T>(T first, T second) where T : IComparable<T> =>
first.CompareTo(second) >= 0 ? first : second;
public class SimpleStack<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("The stack is empty.");
T item = _items[^1];
_items.RemoveAt(_items.Count - 1);
return item;
}
}var item = Create<Widget>();
Console.WriteLine(item.Name); // new widget
static T Create<T>() where T : class, IHasName, new()
{
var instance = new T();
instance.Name = "new widget";
return instance;
}
public interface IHasName { string Name { get; set; } }
public class Widget : IHasName
{
public string Name { get; set; } = "";
}IEnumerable<string> names = new List<string> { "Asha", "Ben" };
IEnumerable<object> objects = names; // allowed: covariance
Console.WriteLine(objects.Count()); // 2