Generics, Constraints, Covariance & Contravariance Masterclass
Welcome to Phase 7 (Chapter 20): C# Generics, Constraints, Covariance & Contravariance Masterclass! Generics allow you to define classes, methods, interfaces, and delegates with placeholder type parameters (<T>). In this lesson, we master type-safe reusable code, generic constraints (where T : class, new(), IComparable), multiple type parameters, covariance (out), and contravariance (in).
Without generics, creating reusable components required using object, which causes runtime type cast errors and boxing performance hits. Generics enforce **compile-time type safety**.
// Generic Method
static T GetFirst<T>(List<T> items)
{
if (items == null || items.Count == 0)
throw new InvalidOperationException("List is empty!");
return items[0];
}
List<int> numbers = new() { 10, 20, 30 };
Console.WriteLine($"First number: {GetFirst(numbers)}"); // Output: 10
List<string> names = new() { "Ravi", "Alice", "Bob" };
Console.WriteLine($"First name: {GetFirst(names)}"); // Output: Ravi
// Generic Class
public class DataRepository<T>
{
private List<T> _storage = new();
public void Add(T item) => _storage.Add(item);
public T Get(int index) => _storage[index];
public int Count => _storage.Count;
}
DataRepository<double> doubleRepo = new();
doubleRepo.Add(99.95);
Console.WriteLine($"Repo item: {doubleRepo.Get(0)}");
Generic constraints restrict the types that can be passed as arguments for type parameter T, allowing access to methods defined on those constrained types:
| Constraint Syntax | Requirement for Type Argument T |
|---|---|
where T : struct | Must be a non-nullable Value Type (int, double, struct). |
where T : class | Must be a Reference Type (class, interface, delegate, string). |
where T : new() | Must have a public parameterless constructor. |
where T : BaseClass | Must derive from specified BaseClass. |
where T : ISomeInterface | Must implement specified Interface. |
public class Repository<TEntity> where TEntity : class, new()
{
public TEntity CreateInstance()
{
return new TEntity(); // Valid because of new() constraint!
}
}
Q1: What is Covariance (out) and Contravariance (in)?
Covariance (IEnumerable<out T>) allows you to use a more derived type than originally specified. Contravariance (Action<in T>) allows you to use a more generic base type.