C# mid-level day 3: generics, collections, delegates and LINQ
The third instalment of a C# learning series walks through generics, the collection types, delegates and lambdas with closures, events, LINQ and iterators. Each topic gets a short definition, an analogy and a sample, followed by six practice projects with full solutions and a rationale for the design choices. All code targets .NET 8 or later with top-level statements, so each solution runs in a fresh console project.
Key points
- Generic code cannot touch type members without a constraint such as where T : class, IEntity; the repository example shows why
- A Dictionary plus TryAdd gives constant-time Get/Remove and duplicate rejection in one call inside a generic repository
- The data structure is the specification: Queue for a print queue, two Stacks for undo/redo, no sorting or index tricks
- Prefer TryDequeue and TryPop when an empty collection is a normal state rather than an error
- A new edit must clear the redo stack, otherwise the history drifts out of sync
- Every solution targets .NET 8+ with top-level statements and runs in a fresh dotnet new console project
The series aims to move a developer past syntax and the object model into the parts of C# that make code expressive. The generics section shows how a type parameter and constraints such as `where T : IComparable<T>` are enforced by the compiler, then builds a generic in-memory repository over `IRepository<T>` where `class, IEntity` makes `item.Id` usable as a `Dictionary` key and `TryAdd` rejects duplicates in a single step.
The collections part argues that picking `HashSet<T>`, `Queue<T>`, `Stack<T>` or `SortedDictionary` is a design decision: a print queue is first-in-first-out and undo is last-in-first-out, so no sorting or index juggling is needed. It also pushes `TryDequeue` and `TryPop` for empty-is-normal cases and warns that a new edit must clear the redo stack. Later sections cover delegates, `Func`/`Action`/`Predicate`, lambdas and closures, events, LINQ and `yield`-based iterators.
Real-world use
- Building a small in-memory data layer or test fake that shares one CRUD skeleton across several entities
- Modelling FIFO behaviour with Queue in a background worker that processes jobs
- Implementing undo/redo history with two Stacks in a text or form editor
- Practising returning the narrowest interface (IEnumerable<T>, IReadOnlyList<T>) from collection-returning APIs
Why it matters
Generics, collection choice and delegates/LINQ map directly onto the ASP.NET Core and EF Core code a developer writes daily, and the exercises build the reflexes of compile-time type safety and picking the right data structure.