Generics
Generics parameterize types — List<T>, Dictionary<TKey,TValue>, your own Repository<TEntity> — providing compile-time type safety without boxing value types.
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
- Compiler generates specialized code or shares reference type instantiations.
- where T : IEntity enables T.Id access.
- Generic method static or instance.
- Cannot cast List
to List — invariant. - Can cast IEnumerable
to IEnumerable — covariant out. - Register typeof(IRepository<>) open generic in DI.
Practical code example
Generic repository with constraint and DI open generic registration pattern:
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, IEntityconstrains 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> _storeindexes 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] = entityinserts test data using the constrainedIdproperty.typeof(IRepository<>)open generic registration in DI resolvesIRepository<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?
Q2Beginnerwhere T : class meaning?
Q3IntermediateCovariance example?
Q4IntermediateOpen generic DI registration?
Q5AdvancedImplement generic cache GetOrAdd thread-safe.
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.