17 questions found
What is covariance ('out T') and contravariance ('in T') in generic interfaces, and how does IEnumerable<out T> demonstrate covariance?
Intermediate
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
How would you implement a generic method with MULTIPLE type parameters and constraints that reference EACH OTHER, like a generic mapping function?
Advanced
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
How do generic type parameters interact with static members, and why does each closed generic type get its OWN independent set of static field values?
Advanced
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
How would you implement a generic, reusable object pool using generic constraints to ensure poolable objects can be properly reset?
Advanced
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
How do generic attributes (introduced in C# 11) work, and what limitation did they finally remove compared to older attribute usage patterns?
Advanced
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
How would you implement generic operator constraints using .NET's generic math interfaces (like INumber<T>) to write a single Sum<T>() method working across int, double, and decimal?
Advanced
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
What are generics in C#, and what specific problems do they solve compared to writing type-specific code?
Intermediate
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