Generics

17 questions found

What are generics and why use them?

Beginner
Generics let you write type-parameterised classes/methods that work with any type while keeping compile-time type safety and avoiding boxing.
public class Box<T> { public T Value { get; set; } }
var b = new Box<int> { Value = 5 };
Real-world example List<T> and Dictionary<K,V> give type-safe collections without casts.

What are generic constraints and why are they needed?

Intermediate
Constraints (where T : ...) restrict the type argument so you can use its members — e.g. class, struct, new(), a base type, or an interface.
T Create<T>() where T : new() => new T();
void Sort<T>(List<T> x) where T : IComparable<T> {}
Real-world example Constraining a repository's entity to a base Entity type so you can read its Id.

What is covariance and contravariance in generics?

Intermediate
out (covariant) lets you use a more-derived type where a less-derived is expected (IEnumerable<out T>); in (contravariant) lets a less-derived be used where more-derived is expected (IComparer<in T>).
IEnumerable<object> objs = new List<string>(); // covariance
Real-world example Returning IEnumerable<Animal> from a method that actually yields Dogs.

How are generics implemented for value types vs reference types in the CLR?

Advanced
The JIT creates a specialised instantiation per value type (no boxing, better perf) but shares a single instantiation for all reference types, since they are the same size (a pointer).
List<int>   // dedicated code, no boxing
List<string>, List<object> // share one instantiation
Real-world example This is why List<int> outperforms ArrayList — no boxing per element.

What problem do generic math / static abstract interface members solve?

Advanced
They let you write algorithms over any numeric type by constraining to interfaces like INumber<T> with static abstract operators, removing duplicated overloads.
T Sum<T>(IEnumerable<T> xs) where T : INumber<T> {
    T total = T.Zero; foreach (var x in xs) total += x; return total; }
Real-world example One Sum method that works for int, double and decimal alike.

Why can't you use operators like + directly on an unconstrained generic T?

Intermediate
Because the compiler doesn't know T supports the operator. You must constrain T (e.g. to INumber<T>) or pass a delegate/strategy that performs the operation.
// error without constraint: T r = a + b;
T Add<T>(T a, T b) where T : INumber<T> => a + b;
Real-world example Generic aggregation helpers require a numeric constraint to add values.

What problem do generics solve compared to writing a method or class that accepts 'object'?

Beginner
Using 'object' loses type safety entirely — you'd need to cast every value back to its real type manually, risking runtime InvalidCastExceptions, and value types get boxed. Generics let a class or method work with MULTIPLE specific types while the compiler enforces type safety and avoids unnecessary boxing, checked entirely at compile time.
// Using object: loses type safety, requires casting, boxes value types
public object GetFirst(object[] items) => items[0];

// Using generics: fully type-safe, no casting, no boxing for value types
public T GetFirst<T>(T[] items) => items[0];
int first = GetFirst(new int[] { 1, 2, 3 }); // no cast needed, no boxing
Real-world example Writing a single, reusable Stack<T> or Repository<T> class that works safely and efficiently with any specific type.

Common follow-ups: Why specifically does using 'object' for value types cause boxing, and why is that a performance concern?

Structs Boxing & Unboxing

How do you declare a generic class with a type parameter, and how do you instantiate it with a specific type?

Beginner
Add `<T>` (or any name, conventionally T, TKey, TValue, etc.) after the class name to declare a type parameter usable throughout the class body; specify the concrete type in angle brackets when creating an instance with 'new'.
public class Box<T> {
  private T _value;
  public Box(T value) { _value = value; }
  public T GetValue() => _value;
}

Box<int> intBox = new Box<int>(42);
Box<string> stringBox = new Box<string>("hello");
Real-world example Building a reusable Box<T>, Repository<T>, or Cache<T> class usable with any specific entity type.

Common follow-ups: Can a generic class have MULTIPLE type parameters, like Dictionary<TKey, TValue>?

Collections

How do generic type constraints ('where T : ...') restrict what types can be used, and what are the common constraint kinds?

Intermediate
Constraints restrict which types are valid for a type parameter, letting you safely use members that constraint guarantees exist — common constraints include 'class' (reference types only), 'struct' (value types only), 'new()' (must have a public parameterless constructor), a specific base class/interface, or 'notnull'.
public class Repository<T> where T : class, IEntity, new() {
  public T CreateNew() => new T(); // safe: 'new()' constraint guarantees this works
  public void Validate(T entity) => entity.Validate(); // safe: IEntity constraint guarantees this method exists
}
Real-world example Constraining a generic repository or factory class to only work with types that are actual entities with an ID and a parameterless constructor.

Common follow-ups: Can you combine MULTIPLE constraints on the same type parameter, and in what order must they appear?

Interfaces & Abstract Classes

How does generic type inference work when calling a generic method, and when do you need to specify the type argument explicitly?

Intermediate
The compiler infers a generic method's type argument(s) from the ARGUMENTS you actually pass, when possible — you only need to specify the type argument explicitly in angle brackets when it CAN'T be inferred from the arguments alone, like when the type only appears in the return type or the method has no arguments at all.
public T CreateDefault<T>() where T : new() => new T();

// Can't infer T from arguments (there are none) -- must specify explicitly
var user = CreateDefault<User>();

public void Print<T>(T value) => Console.WriteLine(value);
Print(42); // T inferred as int automatically from the argument
Real-world example Understanding exactly when you're required to write `<T>` explicitly versus when the compiler figures it out for you.

Common follow-ups: What happens if the compiler CAN'T unambiguously infer T even from the provided arguments?

Fundamentals

Showing 1–10 of 17