Courses/C#/Generics: Advanced

    Generics: Constraints, Variance & Advanced Use Cases

    Lesson 20 โ€ข Advanced Track

    What You'll Learn

    • Apply type constraints (where T : struct, class, new(), interface) to generic types
    • Build generic interfaces like IRepository<T> for reusable data patterns
    • Understand covariance (out T) โ€” why IEnumerable<Dog> can be IEnumerable<Animal>
    • Understand contravariance (in T) โ€” why Action<Animal> can be Action<Dog>
    • Design generic classes and methods that are type-safe and flexible
    • Apply generics to real-world patterns like the Repository pattern

    ๐Ÿ’ก Real-World Analogy

    Constraints are like job requirements โ€” "this position requires someone who can drive (IComparable) and has a clean record (struct)." Covariance is like a delivery service: if you can deliver German Shepherds, you can fill an order for "deliver any dog." Contravariance is the opposite: if a vet treats all animals, they can certainly treat dogs โ€” the more general skill applies to specific cases.

    ๐Ÿ“Š Constraint Quick Reference

    ConstraintMeaningExample
    where T : structValue type onlyint, double, DateTime
    where T : classReference type onlystring, List, custom classes
    where T : new()Has parameterless constructorCan use new T()
    where T : IComparable<T>Implements interfaceCan call CompareTo()
    where T : BaseClassInherits from classGuarantees base members
    out T (covariant)Can substitute derivedIEnumerable<out T>
    in T (contravariant)Can substitute baseAction<in T>

    1. Type Constraints

    Constraints restrict what types can be used as generic arguments. Without constraints, T is treated as object โ€” you can't call CompareTo or use new T(). Add constraints to unlock specific capabilities while keeping type safety.

    Generic Constraints

    Apply struct, class, new(), and interface constraints to generic methods.

    Try it Yourself ยป
    C#
    using System;
    
    // Generic method with constraints
    class DataProcessor
    {
        // where T : struct โ€” value types only (int, double, DateTime)
        static T Max<T>(T a, T b) where T : struct, IComparable<T>
            => a.CompareTo(b) >= 0 ? a : b;
    
        // where T : class โ€” reference types only
        static void PrintIfNotNull<T>(T? item) where T : class
            => Console.WriteLine(item?.ToString() ?? "(null)");
    
        // where T : new() โ€” must have a parameterless constructor
        static T CreateAndLog<T>
    ...

    2. Generic Interfaces & Repository Pattern

    Generic interfaces define contracts that work with any type. The Repository pattern is a classic example: IRepository<T> provides Add, GetById, and GetAll for any entity type. One implementation, infinite reuse.

    Generic Repository Pattern

    Build a reusable IRepository<T> interface with InMemoryRepository.

    Try it Yourself ยป
    C#
    using System;
    using System.Collections.Generic;
    
    // Generic repository pattern
    interface IRepository<T> where T : class
    {
        void Add(T entity);
        T? GetById(int id);
        IEnumerable<T> GetAll();
        int Count { get; }
    }
    
    class InMemoryRepository<T> : IRepository<T> where T : class
    {
        private readonly List<T> _items = new();
        private int _nextId = 1;
    
        public void Add(T entity)
        {
            _items.Add(entity);
            Console.WriteLine($"  Added {typeof(T).Name} #{_nextId++}");
        
    ...

    3. Covariance & Contravariance

    Covariance (out T) means a producer of Dogs can be treated as a producer of Animals. Contravariance (in T) means a consumer of Animals can be treated as a consumer of Dogs. These enable flexible type substitution while maintaining safety.

    Covariance & Contravariance

    Use out T and in T for flexible type substitution.

    Try it Yourself ยป
    C#
    using System;
    using System.Collections.Generic;
    
    // Covariance (out T) โ€” can return T but not accept T as parameter
    interface IProducer<out T>
    {
        T Produce();
    }
    
    // Contravariance (in T) โ€” can accept T but not return T
    interface IConsumer<in T>
    {
        void Consume(T item);
    }
    
    class Animal { public virtual string Sound => "..."; }
    class Dog : Animal { public override string Sound => "Woof!"; }
    class Cat : Animal { public override string Sound => "Meow!"; }
    
    class DogFactory : IProducer<Dog>
    {
     
    ...

    Pro Tips

    • ๐Ÿ’ก Prefer interface constraints over class constraints: where T : IComparable<T> is more flexible than where T : MyBaseClass.
    • ๐Ÿ’ก Remember the mnemonic: out = output position (return types) = covariant. in = input position (parameters) = contravariant.
    • ๐Ÿ’ก Variance only works on interfaces and delegates: You can't make List<Dog> covariant โ€” use IEnumerable<Dog> instead.
    • ๐Ÿ’ก Use multiple constraints: where T : class, IDisposable, new() โ€” all constraints must be satisfied.

    Common Mistakes

    • List<Dog> to List<Animal>: This is NOT allowed โ€” List is invariant. If it were allowed, you could add a Cat to a Dog list. Use IEnumerable<Animal> instead.
    • Missing new() constraint: new T() won't compile without where T : new(). The compiler can't guarantee T has a parameterless constructor.
    • Constraint order matters: class or struct must come first, then interfaces, then new() last.
    • Over-constraining: Adding too many constraints makes the generic less reusable. Only constrain what you actually need.

    ๐ŸŽ‰ Lesson Complete

    • โœ… Constraints restrict generic types: struct, class, new(), interfaces, base classes
    • โœ… Generic interfaces like IRepository<T> enable reusable patterns
    • โœ… Covariance (out T): IProducer<Dog> โ†’ IProducer<Animal>
    • โœ… Contravariance (in T): IConsumer<Animal> โ†’ IConsumer<Dog>
    • โœ… Variance only applies to interfaces and delegates, not classes
    • โœ… Next lesson: Asynchronous Programming Internals

    Sign up for free to track which lessons you've completed and get learning reminders.

    Previous

    Cookie & Privacy Settings

    We use cookies to improve your experience, analyze traffic, and show personalized ads. You can manage your preferences below.

    By clicking "Accept All", you consent to our use of cookies for analytics and personalized advertising. You can customize your preferences or reject non-essential cookies.

    Privacy Policy โ€ข Terms of Service