loading experience

General

Design Patterns in C#: which ones actually matter in 2026?

A guide to design patterns in C# in 2026: which are obsolete, which are baked into the .NET runtime, and how to write modern code without false dogmas.

Design Patterns in C#: which ones actually matter in 2026?

For decades, the Gang of Four (GoF) was the sacred text of software engineering. Learning design patterns was a rite of passage: a rigid checklist (Singleton, Factory, Strategy, Observer...) applied with blind faith that, by piling up abstractions, the architecture would magically become "clean, decoupled and scalable".

In 2026, the technology landscape has forced a drastic course correction. The .NET ecosystem (matured through the latest evolutions of .NET 9 and 10), the pervasive spread of cloud-native, the constraints of Native AOT (Ahead-Of-Time) compilation, and the need to reduce cognitive load have redefined the value of these patterns.

Some have been absorbed into the language and runtime; others have evolved into infrastructural constructs; still others are today considered outright anti-patterns. It's not architecture that's dead, but the dogmatic cult of patterns.

Here's a deep, unfiltered analysis of what actually matters today in C# development.


1. Dependency Injection: From Pattern to Invisible Infrastructure


Dependency Injection (DI) can no longer be considered a simple application-level design pattern: today it's the mental framework on which the entire .NET runtime rests.

[HTTP Request] ──> [Minimal API Endpoint]
▼ (Lifetime: Scoped)
[Business Service] ──> [DbContext / HTTP Client]
▼ (Lifetime: Transient)
[Transient Utilities]

What changed in 2026

With the consolidation of Microsoft's native DI container (and the introduction of advanced features like Keyed Services), we no longer spend time writing Service Locators or custom registration factories. DI is everywhere: from Minimal APIs to Serverless functions, all the way to isolated Worker Services.

The real value today

The focus has shifted from abstraction for its own sake (creating an IService interface for every single class) to fine-grained lifetime management (Transient, Scoped, Singleton) and isolation boundaries. In the cloud-native world, a mistake in lifecycle management (like a Captive dependency, where a Singleton captures a Scoped service) directly translates into memory leaks or data corruption under load.


2. Factory: Small, Explicit and Domain-Bound


The past was dominated by AbstractFactory and class hierarchies created solely to instantiate other classes. A legacy from the early-2000s Java world that has almost entirely lost its meaning in C#.

Today's reality

The only Factories that survived with dignity are the ones that encapsulate genuine business or configuration complexity. Today, lambda expressions, delegates (Func<T>) or primary constructors combined with Keyed Services are widely used instead:

C#


// Modern 2026 solution with Keyed Services
public class PaymentProcessorFactory(IServiceProvider provider)
{
public IPaymentProcessor GetProcessor(PaymentMethod method) => method switch
{
PaymentMethod.CreditCard => provider.GetRequiredKeyedService<IPaymentProcessor>("credit"),
PaymentMethod.PayPal => provider.GetRequiredKeyedService<IPaymentProcessor>("paypal"),
_ => throw new NotSupportedException()
};
}

The modern goal is transparency: the Factory must serve the domain, not be a maze of empty interfaces.


3. Strategy: The Pattern That Ages Best (Thanks to Pattern Matching)


If there's a GoF pattern that hasn't felt the weight of the years, it's Strategy. It's the fundamental pillar for keeping code compliant with the Open/Closed Principle.

The evolution in C#

In the past, implementing Strategy meant creating an interface and ten different classes, bloating the codebase. In modern C#, Strategy has merged with the power of advanced Pattern Matching, records, and higher-order functions (passing delegates).

If the strategy logic is compute-intensive or requires complex dependencies, the separate-classes structure is still ideal. But if it's about lightweight algorithmic variations (e.g. calculating taxes based on country), pattern-matching-based switch expressions offer unbeatable cleanliness and readability, drastically reducing the number of files in the project.


4. Decorator: The King of Cross-Cutting Concerns


How do we handle logging, caching, telemetry, auditing, and retry logic without polluting business services? In 2026 the answer is, unequivocally, the Decorator.

Performance and AOT

In the past, the alternative approach to Decorator was aspect-oriented programming (AOP) based on runtime reflection or dynamic proxy generation (like Castle Core). In 2026, with the massive push toward Native AOT to reduce container startup times and memory usage, heavy reflection is taboo.

The Decorator, applied cleanly through native registration in the DI container (perhaps supported by libraries like Scrutor or via Source Generators), allows layering behavior in a static, safe way with near-zero performance cost.


5. Singleton: Nearly Dead (Wearing a Managed Costume)


"A hand-written Singleton is the architectural equivalent of a global variable wearing a tuxedo."

Why avoid it by hand

The classic implementation with a private static instance, private constructor, and an Instance property (perhaps with double-checked locking for thread safety) is a dinosaur of the past. It makes code untestable, creates rigid coupling, and hides dependencies.

How it lives on in 2026

The concept of a single instance still lives on, but its responsibility is entirely delegated to the Dependency Injection container (builder.Services.AddSingleton<T>). The class itself remains a normal, open, testable class free of self-instantiation logic. Moreover, in cloud architectures with high horizontal scalability, global in-memory state is a risk: anything that is a Singleton must be rigorously stateless.


6. Observer: Gone from the Code, Alive in the Infrastructure


Nobody writes ISubject and IObserver interfaces by hand anymore. The Observer pattern has gone vertical and moved to different levels of the development chain.

  1. At the language level: It has been integrated for years via events and delegates, and evolved with reactive streams (IObservable<T> / Reactive Extensions), though used in specific niches like complex UIs or real-time data stream processing.
  2. At the architectural level: It has transformed into Event-Driven Architecture (EDA). "Observers" today are independent microservices listening for messages on brokers like Kafka, RabbitMQ, or Azure Service Bus. In application code, we use async abstractions like System.Threading.Channels to handle high-performance in-memory producer-consumer scenarios.


7. Command & Mediator: The Standard for Modern APIs


In 2026 web architectures, the Command (encapsulating a request) and Mediator (decoupling sender from executor) pairing represents the de-facto standard, often driven by long-standing libraries like MediatR or custom implementations based on Source Generators.

The push toward Vertical Slice Architecture

The old monolithic controllers and rigidly layered "onion" or "hexagonal" architectures have given way to Vertical Slice Architecture. Instead of organizing code by technical layer (all controllers together, all services together), it's organized by feature.

Every feature is an atomic block made of:

  1. A Command or a Query (an immutable record).
  2. An isolated Handler that contains the sole business logic for that request.
  3. A cross-cutting pipeline (Validation, Logging, Transactionality) managed by the Mediator.

This drastically reduces merge conflicts and makes the code incredibly maintainable.


8. Adapter: The Seatbelt of Integration


The outside world is chaotic. Third-party APIs change, legacy SDKs inherited from old systems don't follow our design standards, and external data models are often redundant or inconsistent with our domain.

The Adapter (or Anti-Corruption Layer in Domain-Driven Design terminology) remains an indispensable tool of architectural hygiene. Isolating the impurity of external systems behind a proprietary interface and a clean adapter is what separates a resilient system from one destined to collapse at the first update of an external NuGet package. In 2026, these mappings are often accelerated by mapping source generators (like Mapperly) to guarantee zero allocations and Native AOT performance.


9. Template Method: Defeated by Composition


Template Method relies on inheritance: a base class defines the skeleton of an algorithm and leaves derived classes the task of overriding certain steps via abstract or virtual methods.

Why it lost popularity

Rigid inheritance has shown all its structural limits over the years. It tends to create fragile hierarchies, where a change to the base class risks breaking unexpected behavior in derived classes (the classic fragile base class problem).

In 2026 the golden rule is: "Composition over Inheritance". The algorithm's skeleton is defined in a standard class that accepts variations of the individual steps not through inheritance, but by receiving strategy interfaces, delegates (Func<T>), or by defining a pipeline of chainable async functions.


10. Repository: A Complicated Relationship with EF Core


The Repository pattern deserves deep reflection, since it's the epicenter of heated debates in the .NET community.

[Anti-Pattern ❌]
Our Services ──> [Generic Repository] ──> [Exposes IQueryable] ──> [EF Core / Database]
(Query logic scatters everywhere, abstraction leak)

[Correct Pattern ]
Our Services ──> [Specific Domain Repository] ──> [Returns Aggregate Root] ──> [EF Core]
(Protection of business invariants, encapsulated queries)

The big misunderstanding


Creating a GenericRepository<T> that exposes methods like IQueryable<T> GetAll() is, in 2026, a widely-acknowledged anti-pattern. Entity Framework Core is already, intrinsically, an implementation of the Repository pattern (via DbSet) and the Unit of Work pattern (via DbContext). Creating a generic wrapper on top of it only hides crucial functionality (like tracking, explicit loading of relationships, or compiled query performance) without offering any real advantage.

When it makes sense

Repository is alive and useful only if applied according to the canons of Domain-Driven Design (DDD):

  1. It's specific to an Aggregate Root (not a repository for every single database table).
  2. It doesn't expose IQueryable, but returns fully hydrated domain objects (Entity/Record) ready to execute business logic.
  3. It completely hides persistence details, acting as an in-memory collection of domain objects.


Architectural Summary for 2026


PatternStatus in 2026Modern Substitute / Evolution
Dependency Injection🌟 EssentialNatively built into the .NET runtime.
Strategy🌟 ExcellentOptimized via Pattern Matching and Switch Expressions.
Command / Mediator🌟 StandardBackbone of Vertical Slice Architectures and Minimal APIs.
Decorator🌟 ExcellentFundamental for cross-cutting concerns, especially with Native AOT in mind.
Adapter🚀 UsefulUsed as Anti-Corruption Layer at system boundaries.
Factory⚠️ ConditionalOnly if explicit and lean; often integrated with Keyed Services.
Repository⚠️ ConditionalOnly if specific to Aggregate Roots; avoid if it's a generic EF Core wrapper.
Observer🔄 TransformedEvolved into Event-Driven Architectures and async cloud messaging.
Singleton❌ SupersededManaged exclusively as a DI container lifetime.
Template Method❌ DeprecatedReplaced by composition of functions and delegate-based pipelines.


Conclusion: Less Dogma, More Intent

The dividing line between successful software and an architectural failure in 2026 isn't determined by how many GoF patterns were implemented. It's determined by the code's ability to reduce non-linear complexity, make business intent explicit, and let the team ship value to production safely and at high performance.

Patterns aren't rules carved in stone; they're historical solutions to common problems. If the evolution of the language and infrastructure offers a simpler, more linear, and more performant way to solve the same problem, abandoning the old pattern isn't a design sin: it's engineering pragmatism. And in 2026, pragmatism always wins over nostalgia.

Comments (0)

No comments yet.

Leave a comment