C# Programming Tutorial 0/45 lessons ~6 min read Lesson 36

    Generics

    Generics parameterize types — List<T>, Dictionary<TKey,TValue>, your own Repository<TEntity> — providing compile-time type safety without boxing value types.

    Course progress0%
    Focus
    10 guided sections
    Practice signal
    Examples included
    Career prep
    Interview Q&A included

    Introduction

    Generics parameterize types — List<T>, Dictionary<TKey,TValue>, your own Repository<TEntity> — providing compile-time type safety without boxing value types. Constraints (where T : class) limit type parameters.

    Interviewers ask covariance/out, contravariance/in, and generic method vs class. Reflection and open generic registration power DI containers.

    The story

    A startup builds one in-memory repository implementation that works for customers, orders, and invoices — as long as each entity has a Guid Id. Generic constraints (where T : class, IEntity) let the compiler enforce that contract, and DI registers IRepository<> as an open generic so every entity gets a repository without copy-paste code.

    Understanding the topic

    Key concepts

    • Generic class/method/interface with type parameters.
    • Constraints: class, struct, new(), base class, interface.
    • Generic methods infer T from arguments.
    • Covariance IEnumerable; contravariance Action.
    • Open generic typeof(List<>) for DI registration.
    • Default constraint allows T? for struct.

    Step-by-step explanation

    1. Compiler generates specialized code or shares reference type instantiations.
    2. where T : IEntity enables T.Id access.
    3. Generic method static or instance.
    4. Cannot cast List to List — invariant.
    5. Can cast IEnumerable to IEnumerable — covariant out.
    6. Register typeof(IRepository<>) open generic in DI.

    Practical code example

    Generic repository with constraint and DI open generic registration pattern:

    csharp
    namespace TechLearningPro.Generics;
    public interface IEntity { Guid Id { get; } }
    public interface IRepository<T> where T : class, IEntity
    {
    Task<T?> GetByIdAsync(Guid id, CancellationToken ct);
    }
    public sealed class InMemoryRepository<T> : IRepository<T> where T : class, IEntity
    {
    private readonly Dictionary<Guid, T> _store = new();
    public Task<T?> GetByIdAsync(Guid id, CancellationToken ct) =>
    Task.FromResult(_store.GetValueOrDefault(id));
    public void Seed(T entity) => _store[entity.Id] = entity;
    }
    // DI: services.AddSingleton(typeof(IRepository<>), typeof(InMemoryRepository<>));

    Line-by-line code explanation

    • interface IEntity { Guid Id { get; } } defines the minimum shape all stored entities must have.
    • interface IRepository<T> where T : class, IEntity constrains T to reference types implementing IEntity.
    • Task<T?> GetByIdAsync(Guid id, ...) returns a nullable entity when the ID is not found.
    • InMemoryRepository<T> : IRepository<T> is the generic concrete implementation.
    • Dictionary<Guid, T> _store indexes entities by their GUID primary key.
    • Task.FromResult(_store.GetValueOrDefault(id)) wraps the lookup in a completed Task for async consistency.
    • Seed(T entity) => _store[entity.Id] = entity inserts test data using the constrained Id property.
    • typeof(IRepository<>) open generic registration in DI resolves IRepository<Customer> automatically.

    Key takeaway: where T : class, IEntity combines reference constraint with interface. Open generic DI registration serves any entity type.

    Real-world use

    Where you'll use this in production

    • Repository and service layers parameterized by entity.
    • PagedResult API response wrappers.
    • Generic algorithms Max where T : IComparable.
    • Option/Maybe functional types.

    Best practices

    • Apply minimal constraints needed.
    • Prefer generic methods when only one method generic.
    • Name type params T, TKey, TResult meaningfully when multiple.
    • Avoid generic exceptions and excessive complexity.
    • Document constraints in XML docs.

    Common mistakes

    • Over-constraining T blocking valid types.
    • Assuming List covariance — it's invariant.
    • new() constraint on interface types — invalid.
    • Generic type in catch clause — not allowed.

    Advanced interview questions

    Q1BeginnerWhy generics?
    Type safety, performance without boxing, reusable algorithms.
    Q2Beginnerwhere T : class meaning?
    T must be reference type — nullable reference type rules apply.
    Q3IntermediateCovariance example?
    IEnumerable — Dog enumerable usable as Animal enumerable.
    Q4IntermediateOpen generic DI registration?
    services.AddScoped(typeof(IRepo<>), typeof(EfRepo<>));
    Q5AdvancedImplement generic cache GetOrAdd thread-safe.
    ConcurrentDictionary<(Type, string), object>; GetOrAdd with factory Func; lock double-check or Lazy.

    Summary

    Generics enable type-safe reusable code. Constraints express required capabilities. Understand covariance limits on collections. Open generics integrate with DI containers. Next: extension methods.

    Ready to mark this lesson complete?Track your journey across the entire course.