iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Generics

C# generics give type-safe reusable code: List<T>, Dictionary<K, V>, your own generic types. Compile-time checked; CLR specialises per value-type T; no boxing.

Generic classes, methods, constraints

EXAMPLE
using System;
using System.Collections.Generic;

// 1) Generic class
public class Stack<T>
{
    private readonly List<T> items = new();

    public int Count => items.Count;

    public void Push(T item) => items.Add(item);

    public T Pop()
    {
        if (items.Count == 0) throw new InvalidOperationException();
        var top = items[^1];
        items.RemoveAt(items.Count - 1);
        return top;
    }

    public T Peek() => items[^1];
}

var nums = new Stack<int>();
nums.Push(1);
nums.Push(2);
nums.Pop();   // 2

var names = new Stack<string>();
names.Push("Ada");

// 2) Generic method (independent of the class)
public static T First<T>(IEnumerable<T> items)
{
    foreach (var item in items) return item;
    throw new InvalidOperationException();
}

var f = First(new[] { 10, 20, 30 });          // 10

// 3) Multiple type parameters
public class Pair<TFirst, TSecond>
{
    public TFirst  First  { get; set; } = default!;
    public TSecond Second { get; set; } = default!;
}

var p = new Pair<string, int> { First = "Ada", Second = 32 };

// 4) Constraints — restrict T
public static T Max<T>(T a, T b) where T : IComparable<T>
{
    return a.CompareTo(b) > 0 ? a : b;
}

Max(3, 7);            // 7
Max("apple", "banana");

// Multiple constraints
public static T Build<T>() where T : class, new()    // ref type AND has parameterless ctor
{
    return new T();
}

var list = Build<List<int>>();

// Common constraint clauses:
//   where T : struct           — value type
//   where T : class            — reference type
//   where T : new()            — has a parameterless ctor
//   where T : SomeBase         — derives from SomeBase
//   where T : ISomeInterface   — implements ISomeInterface
//   where T : notnull          — cannot be null
//   where T : unmanaged        — unmanaged value type
//   where T : U                — T derives from another type param U

// 5) Generic interface
public interface IRepository<T, TId>
    where T  : class
    where TId: IEquatable<TId>
{
    Task<T?>       FindAsync(TId id);
    Task<List<T>>  AllAsync();
    Task<T>        SaveAsync(T entity);
    Task<bool>     DeleteAsync(TId id);
}

// 6) Covariance + contravariance (in / out modifiers)
public interface IProducer<out T>            // covariant — can return T
{
    T Get();
}

public interface IConsumer<in T>             // contravariant — can accept T
{
    void Send(T item);
}

IProducer<Animal>  animals = new ProducerImpl<Dog>();   // OK — Dog producer is an Animal producer
IConsumer<Dog>     dogs    = new ConsumerImpl<Animal>(); // OK — accepts any Dog if it accepts any Animal

// 7) Default values for T
public class Cache<T>
{
    private T value = default!;     // default(T) — null for ref types, 0/false/etc. for value types
}

// 8) Generic delegates
public delegate TResult Transformer<T, TResult>(T input);

Transformer<int, string> intToString = n => n.ToString();
intToString(42);                                  // "42"

// Standard library delegates — already generic
//   Action<T1, ...>           : void f(T1, ...)
//   Func<T1, ..., TResult>    : TResult f(T1, ...)
//   Predicate<T>              : bool f(T)
//   Comparison<T>             : int f(T, T)

// 9) Generic methods can infer T
void Print<T>(T value) => Console.WriteLine(value);

Print(42);            // T inferred as int
Print("hello");       // T inferred as string
Print<int>(42);       // explicit

// 10) Generic + LINQ — most C# generics you use
List<User> users = ...;
var emails = users.Select(u => u.Email).Distinct().ToList();
var admins = users.Where(u => u.Role == "admin").ToList();

// 11) Real-world generic types
//   List<T>, Dictionary<K, V>, HashSet<T>, Queue<T>, Stack<T>, LinkedList<T>
//   Nullable<T> (T?)
//   Lazy<T>
//   Task<T>, IAsyncEnumerable<T>
//   IEnumerable<T>, IReadOnlyList<T>, IReadOnlyDictionary<K, V>
//   Tuple<T1, ...>, ValueTuple<T1, ...>
//   Func<T, R>, Action<T>, Predicate<T>
//   Result<TOk, TErr> (custom — usually defined per project)

// 12) Generic + record
public record Result<TOk, TErr>(TOk? Value, TErr? Error)
{
    public bool IsOk => Error is null;
}

// 13) Where to use generics
// ✅ Collections / containers
// ✅ DAO / Repository pattern
// ✅ Functional helpers (Map, Filter, Reduce alternatives)
// ✅ Async results (Task<T>, AsyncEnumerable<T>)
// ✅ Generic event payloads (EventArgs<T>)

// 14) Where NOT to use generics
// ❌ When T only ever has 1-2 concrete types — just write 2 methods
// ❌ When constraints get out of hand (5+ where clauses) — refactor
// ❌ When you just want polymorphism — use an interface

// 15) Generic math (C# 11+) — static abstract members in interfaces
public static T Sum<T>(IEnumerable<T> items) where T : INumber<T>
{
    var sum = T.Zero;
    foreach (var x in items) sum += x;
    return sum;
}

Sum(new[] { 1, 2, 3 });          // 6
Sum(new[] { 1.5, 2.5 });          // 4.0

// 16) Common bugs
//   • Forgetting constraints → compiler can't infer behaviour, errors on operations
//   • Using default(T) on non-nullable ref types without #nullable annotations → CS8601
//   • Covariance / contravariance on mutable types → write-time errors
//   • Capturing T in lambdas without closure-safe access
//   • Generic + dynamic — compile-time generics don't see runtime types

// 17) Tips
//   • Use generic interfaces (IRepository<T, ID>) for cross-cutting infrastructure
//   • Constraints are documentation — code is much clearer when they're explicit
//   • Prefer Func<T, R> / Action<T> over custom delegates when possible
//   • Don't ship 'AnyType' generics (T = object); usually a sign of bad design
//   • Use 'in' / 'out' modifiers for variance when the API allows

Why it matters

C# generics are zero-cost and type-safe; the CLR specialises for value types so there’s no boxing. Pair with constraints (where T : IComparable<T>) to keep the API honest — the compiler then guides everyone who calls into your code.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
public class Box<T> {
    public T Item { get; set; }
}
var b = new Box<int> { Item = 42 };
Try it Yourself »

Discussion

Loading…