Generics

17 questions found

Covariance ('out T') allows a generic interface with a type parameter used only in OUTPUT positions (return values) to be treated as its LESS specific base type automatically — e.g., IEnumerable<Dog> can be used where IEnumerable<Animal> is expected, since you can only READ Dogs out of it as Animals safely; contravariance ('in T') is the reverse, for type parameters used only in INPUT positions.
IEnumerable<string> strings = new List<string> { "a", "b" };
IEnumerable<object> objects = strings; // OK! covariant: IEnumerable<out T> allows this

// Contravariance example:
Action<object> objectAction = obj => Console.WriteLine(obj);
Action<string> stringAction = objectAction; // OK! contravariant: Action<in T> allows this
Real-world example Understanding why you can assign an IEnumerable<Dog> directly to an IEnumerable<Animal>-typed variable without any explicit conversion.

Common follow-ups: Why can't a MUTABLE generic interface like IList<T> be covariant the same way IEnumerable<T> is?

Interfaces & Abstract Classes
You can declare several type parameters on the same method/class, each with independent (or interdependent) constraints — a common pattern is a generic converter/mapper where the source and destination types both need to satisfy their own requirements, letting the compiler enforce correctness across the whole mapping operation.
public TDest Map<TSource, TDest>(TSource source, Func<TSource, TDest> mapper)
    where TSource : class
    where TDest : class, new() {
  return mapper(source);
}

var userDto = Map<User, UserDto>(user, u => new UserDto { Name = u.Name });
Real-world example Building a generic, type-safe mapping utility between domain entities and DTOs with independently-constrained source and destination types.

Common follow-ups: How would you further constrain TDest to guarantee it has a specific interface method needed during mapping?

Interfaces & Abstract Classes
Unlike instance members, STATIC fields on a generic class are NOT shared across different closed generic instantiations — Box<int> and Box<string> each get their OWN, completely independent copy of any static field declared in Box<T>, since the CLR generates a distinct type for each unique combination of generic arguments (for reference types sharing code, but with separate static storage).
public class Counter<T> {
  public static int InstanceCount = 0;
  public Counter() { InstanceCount++; }
}

new Counter<int>(); new Counter<int>();
new Counter<string>();
Console.WriteLine(Counter<int>.InstanceCount);    // 2
Console.WriteLine(Counter<string>.InstanceCount); // 1 -- separate, independent counter!
Real-world example Debugging a surprising bug where a 'shared' static counter on a generic class turned out to be tracking each closed generic type SEPARATELY, not globally.

Common follow-ups: Why does the CLR handle this differently for VALUE TYPE versus REFERENCE TYPE generic instantiations, in terms of code generation?

Memory & Garbage Collection
Constrain the pool's type parameter to an interface (like IPoolable) requiring a Reset() method, letting the generic pool safely call it when returning an object to the pool — this avoids unsafe casting or reflection while keeping the pool fully generic and reusable across any poolable type.
public interface IPoolable { void Reset(); }

public class ObjectPool<T> where T : class, IPoolable, new() {
  private readonly Stack<T> _pool = new();
  public T Rent() => _pool.Count > 0 ? _pool.Pop() : new T();
  public void Return(T item) { item.Reset(); _pool.Push(item); }
}
Real-world example Building a reusable pool for expensive-to-create objects (like StringBuilder or a game entity) to reduce garbage collection pressure in a hot loop.

Common follow-ups: How does this hand-rolled pool compare to the built-in Microsoft.Extensions.ObjectPool library's ObjectPool<T>?

Memory & Garbage Collection
Prior to C# 11, an attribute class couldn't itself be generic (`public class MyAttribute<T> : Attribute` was disallowed), forcing awkward workarounds like passing a Type object as a constructor argument instead; C# 11 allows genuinely generic attribute classes, letting you specify the type argument directly and get compile-time type checking on the attribute's type parameter.
// C# 11+: genuinely generic attribute
public class ValidatorAttribute<T> : Attribute where T : IValidator { }

[Validator<EmailValidator>] // type-checked at compile time
public string Email { get; set; } = "";

// Older workaround before C# 11:
// [Validator(typeof(EmailValidator))] -- no compile-time type constraint enforcement
Real-world example Writing a strongly-typed validation attribute that references a specific validator TYPE with full compile-time type checking.

Common follow-ups: Why couldn't older C# express this same idea with full type safety before generic attributes were introduced?

Attributes & Reflection
Constrain the generic type parameter to INumber<T> (introduced in .NET 7 alongside static abstract interface members), which guarantees standard arithmetic operators (+, -, etc.) and static members like Zero are available — letting you write ONE generic numeric algorithm that works correctly across every built-in (and custom) numeric type without duplicating overloads for each.
public static T Sum<T>(IEnumerable<T> values) where T : INumber<T> {
  T total = T.Zero;
  foreach (var value in values) total += value;
  return total;
}

int intSum = Sum(new[] { 1, 2, 3 });          // 6
double doubleSum = Sum(new[] { 1.5, 2.5 });    // 4.0
Real-world example Writing a single, reusable Sum(), Average(), or Min()/Max() utility working generically across every numeric type without separate overloads.

Common follow-ups: What is a 'static abstract interface member,' and how does it make this specific generic math pattern possible in C# 11+?

Interfaces & Abstract Classes
Generics let you define a class, method, or interface with a placeholder type parameter (like <T>) that's specified when the type is actually used, avoiding the need to write near-identical code for every different data type -- beyond reducing duplication, generics provide genuine compile-time type safety (the compiler rejects an incompatible type at the call site) and better performance than the older, pre-generics approach of using object and relying on boxing/unboxing or unsafe casts.
// Without generics: separate overloads needed per type
public void Print(int value) => Console.WriteLine(value);
public void Print(string value) => Console.WriteLine(value);

// With generics: one method, any type, still fully type-checked
public void Print<T>(T value) => Console.WriteLine(value);

List<int> numbers = new(); // type-safe: only accepts int, unlike the old non-generic ArrayList
Real-world example A generic Repository<T> base class implements common CRUD operations (Add, GetById, Delete) once, reused across UserRepository, ProductRepository, and every other entity-specific repository in the application without duplicating the same logic per entity type.

Common follow-ups: How do generic constraints (where T : IComparable<T>) restrict what types can be used?;Why do generic collections like List<T> outperform their non-generic predecessors like ArrayList?

Generics;Collections Framework

Showing 11–17 of 17