.NET Design Patterns & Best Practices

5 days
UNPAT
5 days

Upcoming Sessions

Date:

Format:

Price:

Location:

Book now

Date:

Format:

Price:

Location:

Book now

Date:

Format:

Price:

Location:

Book now

Date:

Format:

Price:

Book now

Need a private training for your team?  Request a private training

Not ready to book yet?   Request an offer

Improve your OO-design with S.O.L.I.D principles

The S.O.L.I.D principles were introduced by Robert C. Martin (also known as Uncle Bob) in 2000. The intention behind these principles is to make software designs more understandable, easier to maintain, and easier to extend. These principles are essential for every developer to know because they assist in writing better code and in better understanding code that was written with these principles in mind.

  • S - Single Responsibility
  • O - Open/Closed
  • L - Liskov Substitution
  • I - Interface Segregation
  • D - Dependency Inversion

Introduction to Patterns

One might not believe it, but design patterns did not originate in software architecture. Instead, patterns were first described in the 1970s by a real 'bricks and mortar' architect. A pattern is defined as a reusable solution to a common problem, without dictating the exact implementation. This characteristic makes patterns an ideal learning tool and a powerful means for communicating design ideas. Of course, there are also anti-patterns—approaches that are frequently used but are better avoided.

  • What is a Pattern?
  • The Gang of Four: Erich Gamma, Ralph Johnson, Richard Helm and John Vlissides.
  • Different kinds of design patterns: creational, structural and behavioral patterns.
  • Patterns everywhere: the difference between implementation, design and architecture patterns...
  • When to apply patterns, and when not to
  • Some anti-patterns, such as Loosey-Goosey

Singleton Pattern

We will start with a simple creational pattern, Singleton, and discuss its implementation in C#

  • What is a Singleton?
  • Singleton implementation choices in .NET
  • Using Dependency Injection
  • Why sealing classes is a good idea
  • LAB: Building a Singleton in .NET

Code Reuse with Template Method

Many times you need to provide a base class for developers to inherit from. Here we discuss building a more robust version using the template method pattern.

  • Template Method - defer exact parts of an algorithm to inheriting classes, delegates, ...
  • LAB: Building a Template Method for a board game

Abstract Object Creation with Factories

When developing software, you often find yourself continuously creating new objects. While the fundamental act of creating new objects is not inherently problematic, it can lead to design challenges or increased complexity. Creational patterns such as Factory Method can assist you in managing object creation in a way that is appropriate for the situation.

  • Factory Method - delegating/hiding the creation of objects to a factory
  • Abstract Factory - abstracting to a factory to create families of objects
  • LAB: Implementing an Abstract Factory

The Strategy and Chain-Of-Responsibility Pattern

Strategy and Chain-Of-Responsibility are two behavioral patterns that assist in defining a family of algorithms, encapsulating each one, and making them interchangeable. By applying these patterns, you can allow the client to choose the algorithm that best suits their needs at runtime.

  • Strategy - template method without the annoying inheritance
  • LAB: Sorting Objects using Strategies
  • Chain of Responsibility - strategy to go through a chain of strategies
  • LAB: Using a Chain of Responsibility to implement a Builder

Collection Patterns

Iterators are a fundamental part of the .NET framework. They provide a generic way of navigating through collections. By using the yield keyword, you can create an iterator that returns a sequence of values. This approach is particularly useful when you need to asynchronously iterate over a collection.

  • Iterator - providing a generic way of navigating through collections, yield is your friend, asynchronously iterating a collection
  • Performance considerations on implementing IEnumerable
  • Using Frozen collections

Behavioral Pattern: State and State Machines

The State pattern is a behavioral pattern that eliminates sprawling if/switch blocks by delegating state-dependent behavior to dedicated state objects. Each state encapsulates what the object should do in that situation, and transitions are explicit and traceable. For more complex workflows, a formal state machine library makes the model even clearer and easier to visualize.

  • Recognizing the problem: conditional logic scattered across a class
  • State — encapsulating behavior in state objects and making transitions explicit
  • Modeling workflows as finite state machines
  • Using the Stateless library: defining states, triggers, and guards in .NET
  • Visualizing state machines for documentation and debugging
  • LAB: Implementing a media player (Play / Pause / Stop) with the State pattern and the Stateless library

Structural Pattern: Flyweight

The Flyweight pattern is a structural pattern that reduces memory consumption by sharing as much data as possible between a large number of fine-grained objects. It separates intrinsic state (shared, immutable) from extrinsic state (caller-supplied, contextual), so that a single flyweight instance can serve many different contexts simultaneously.

  • Recognizing the problem: thousands of nearly-identical objects consuming excessive memory
  • Flyweight — separating intrinsic (shared) state from extrinsic (contextual) state
  • Implementing a Flyweight factory with a cache in .NET
  • How .NET already applies Flyweight: string interning and small integer caching
  • Trade-offs: when Flyweight helps — and when it adds unnecessary complexity

Structural Pattern: Composite

The Composite pattern lets you treat individual objects and groups of objects uniformly by placing them in a tree structure. It is ideal whenever your domain naturally forms part-whole hierarchies — file systems, UI component trees, organizational charts, or mathematical expression trees.

  • Composite — building part-whole hierarchies with a uniform interface
  • Leaf vs. composite nodes: when to add child-management to the component interface
  • Composite in .NET: expression trees, UI control trees

Behavioral Pattern: Interpreter

The Interpreter pattern defines a grammar for a language and provides an interpreter to process sentences in that language. In .NET, expression trees are the canonical example: LINQ providers translate C# expression objects into SQL, JSON queries, or other target languages at runtime.

  • Interpreter — representing grammar rules as a class hierarchy
  • Building and evaluating an abstract syntax tree (AST)
  • How LINQ expression trees use Interpreter — and how you can build your own
  • LAB: Building a simple arithmetic expression evaluator with Interpreter

Creational Pattern: Builder

The Builder pattern separates the construction of a complex object from its representation, so the same construction process can produce different results. Modern .NET embraces fluent builder APIs throughout — from WebApplication.CreateBuilder() to test data factories — making this a pattern every developer encounters daily.

  • Builder — step-by-step construction of complex objects
  • Fluent builder APIs: method chaining for readable, intention-revealing construction
  • Builder in .NET: WebApplicationBuilder, IHostBuilder, and test data builders
  • Director classes: encapsulating common construction sequences
  • LAB: Parsing expressions using the Builder pattern

Behavioral Pattern: Visitor

The Visitor pattern lets you add new operations to an object structure without modifying its classes. It solves the "expression problem": how do you add new behaviors to a fixed set of types? Modern C# offers two complementary approaches — classic double-dispatch Visitor and pattern-matching switch expressions.

  • Visitor — separating algorithms from the object structure they operate on
  • Double-dispatch: how Accept and Visit work together
  • Building a pretty-printer using classic Visitor
  • Modern alternative: C# pattern matching with switch expressions
  • Using dynamic dispatch to avoid repetitive Accept boilerplate
  • Trade-offs: classic Visitor vs. pattern matching — when to choose each
  • LAB: Implementing a Visitor to generate multiple output formats from the same object hierarchy

Behavioral Pattern: Observer

The Observer pattern defines a one-to-many dependency: when one object changes state, all its dependents are notified automatically. It is the foundation of event-driven programming in .NET — and understanding it deeply helps you avoid common pitfalls like memory leaks, unexpected notification order, and threading issues.

  • Observer — decoupling publishers from subscribers
  • Events and delegates in .NET: the built-in Observer implementation
  • The IObservable<T> / IObserver<T> interfaces as a push-based alternative
  • LAB: Building a stock price ticker using Observer

Behavioral Pattern: Mediator

Where Observer is one-to-many, Mediator centralizes many-to-many communication. Instead of objects knowing about each other, they all talk to a single mediator that coordinates interactions. This dramatically reduces coupling in complex UIs and service layers — and is the pattern behind INotifyPropertyChanged, MediatR, and message bus architectures.

  • Mediator — a central hub replacing direct object-to-object references
  • Observer vs. Mediator: when to use each
  • Correctly implementing INotifyPropertyChanged — and avoiding the stringly-typed anti-pattern
  • Using nameof() and source generators to make property notifications refactor-safe
  • The MediatR library: applying Mediator in a service/handler architecture
  • LAB: Refactoring a tightly coupled form into a Mediator-based design

Structural Pattern: Adapter

The Adapter pattern converts the interface of a class into another interface that clients expect. It lets you plug in components that would otherwise be incompatible — without modifying either side. This is one of the most frequently used patterns in real codebases, wherever you integrate third-party libraries or legacy code.

  • Adapter — bridging incompatible interfaces without touching either class
  • Object adapter vs. class adapter: composition vs. inheritance
  • Adapting third-party libraries to your own domain interfaces
  • Two-way adapters: wrapping both directions
  • LAB: Building an adapter using Expressions and the DLR

Structural Pattern: Decorator

The Decorator pattern attaches additional behavior to an object dynamically by wrapping it in a decorator object that shares the same interface. It is a flexible alternative to subclassing — you can stack multiple decorators and combine behaviors at runtime rather than baking them into a class hierarchy.

  • Decorator — augmenting behavior transparently without changing the caller
  • Stacking decorators: composing cross-cutting concerns (logging, caching, validation)
  • Decorator in .NET: Stream, ASP.NET Core middleware, and DI-based decoration
  • Registering decorators in a DI container
  • LAB: Adding Sorting Operations with Decorators

Structural Pattern: Proxy

The Proxy pattern provides a surrogate that controls access to another object. Unlike Decorator — which adds behavior — Proxy controls whether and when the real object is reached. The three main flavors are virtual proxy (lazy loading), protection proxy (access control), and remote proxy (network transparency).

  • Proxy — controlling access to the real subject
  • Virtual proxy: deferring expensive object creation until it is needed
  • Protection proxy: enforcing authorization before delegating calls
  • Remote proxy: making a network call look like a local method call
  • Proxy vs. Decorator: same structure, different intent

Hide Implementation Details with Facade

Facade is both a GOF pattern and a Microsoft pattern. Here we will discuss the differences and similarities between the two.

  • Facade - hiding the complexity of subsystems from the caller

Using Value Objects

The Value Object pattern represents objects that are defined by their values rather than a unique identity, such as a Money or EmailAddress type. Value objects are immutable and compared by their properties, making your code more robust, intention-revealing, and less error-prone.

  • Avoiding the Primitive Obsession anti-pattern
  • Best practices for building Value Objects
  • LAB: Building an E-mail Value Object

Accessing Data Using the Specification and Repository Pattern

The repository pattern abstracts data access logic by providing a collection-like interface for accessing domain objects, isolating the domain layer from data access concerns. It centralizes CRUD operations, making the codebase more maintainable and testable. The specification pattern allows you to encapsulate business rules or query criteria into reusable, composable objects, making your code more expressive and testable. It helps separate the "what to query" from "how to query," often used with LINQ to filter entities.

  • Repository - abstracting data access
  • LAB: Implementing a Repository using Entity Framework Core
  • Specification - encapsulating complex queries
  • Using the Specification pattern with Entity Framework Core
  • LAB: Implementing a Specification for a Repository

Using the Result Pattern

The result pattern refers to a coding convention where methods return a special type (often named Result, OperationResult, etc.) that encapsulates both success/failure status and associated data or error messages. This pattern improves error handling by avoiding exceptions for flow control and encouraging explicit success/failure checks in the calling code

  • Exceptions are expensive
  • Using the result pattern
  • Improving the result pattern using LINQ syntax
  • LAB: Using the result pattern in an MVC application

Build Flexible UIs with Model-View-Whatever

Most developers are not naturally skilled as graphic designers. This recognition has led to the development of design patterns that enable developers to concentrate on coding the application's behavior while allowing graphic designers to create compelling user interfaces. The cornerstone among these patterns is known as Model-View-Whatever (MVW), with Whatever being adaptable based on the specific technology in use. Grasping the MVW pattern is crucial, as it forms the foundation for developing both Windows and web applications.

  • Model-View-Controller: An ancient pattern back in fashion
  • The ASP.NET MVC Pattern
  • MVVM in Blazor - MVW taking advantage of powerful databinding capabilities
  • Command: Encapsulate behavior in objects
  • Implementing commands using closures

Design Patterns Applied: Developing your own Reusable Library

When do you most need patterns? The answer is particularly when you're developing a framework on your own. Building a framework involves integrating new features while maintaining backward compatibility, a challenge that can be significantly simplified through the proper application of patterns. Therefore, in this final part of the training, we will construct a reusable library. During this process, we will encounter several challenges and address them by applying the appropriate patterns.

  • Adding the GoF Command pattern to MVVM
  • Using interfaces for flexibility
  • Building Command Objects - extending the ICommand interface
  • Adding Undo and Redo functionality to the command pattern
  • Using a CommandManager class
  • Challenge: retro-fitting our commands into MVVM without lots of changes
  • Implementing Undo-Redo using the Memento pattern
  • Choosing whether or not to add the Prototype pattern
  • Ideas on how to proceed with the command pattern
  • LAB: Implementing Undo/Redo logic as a reusable library

So, how can you become a better developer? One of the best ways is to learn about design patterns. Design Patterns provide reusable solutions for common software design challenges. In this training, we identify software design problems and explore how to address them using the most suitable Design Pattern. We will discuss creational, behavioral and structural patterns. And of course we will explore implementation choices in .NET. All examples and labs use the latest LTS version of .NET and Visual Studio. Labs also work with any recent .NET and VS version.

This course is intended for experienced programmers who are familiar with C# and have a working experience of .NET.

Contact Us
  • Address:
    U2U nv/sa
    Z.1. Researchpark 110
    1731 Zellik (Brussels)
    BELGIUM
  • Phone: +32 2 466 00 16
  • Email: info@u2u.be
  • Monday - Friday: 9:00 - 17:00
    Saturday - Sunday: Closed
Say Hi
© 2026 U2U All rights reserved.