The Gang of Four patterns were written for C++ and Smalltalk. While the underlying principles remain sound, modern C# offers language features that dramatically simplify — or entirely replace — many classic pattern implementations. Here is how I implement common patterns in C# 12.
Strategy Pattern with Delegates
The classic Strategy pattern requires an interface, multiple concrete strategy classes, and a context class. In modern C#, a delegate or lambda often suffices.
// Classic approach — lots of ceremony
public interface IDiscountStrategy
{
decimal CalculateDiscount(decimal total);
}
public class PercentageDiscount(decimal percentage) : IDiscountStrategy
{
public decimal CalculateDiscount(decimal total) => total * percentage / 100;
}
// Modern approach — just use a Func
public class PricingService(Func<decimal, decimal> discountStrategy)
{
public decimal CalculateTotal(decimal subtotal)
{
var discount = discountStrategy(subtotal);
return subtotal - discount;
}
}
// Registration
builder.Services.AddSingleton<Func<decimal, decimal>>(total => total * 0.1m);
builder.Services.AddTransient<PricingService>();
Use the interface approach when you need multiple methods, dependency injection, or testability. Use the delegate approach when the strategy is genuinely a single function.
Builder Pattern with Required Members
C# 11's required members and init-only properties make many uses of the Builder pattern unnecessary. The compiler itself enforces that all required properties are set.
// Builder pattern — still useful for complex construction
public class EmailBuilder
{
private string _to = "";
private string _subject = "";
private readonly List<string> _attachments = [];
public EmailBuilder To(string to) { _to = to; return this; }
public EmailBuilder Subject(string subject) { _subject = subject; return this; }
public EmailBuilder Attach(string path) { _attachments.Add(path); return this; }
public Email Build() => new(_to, _subject, [.. _attachments]);
}
// Modern alternative — required members enforce completeness at compile time
public class EmailMessage
{
public required string To { get; init; }
public required string Subject { get; init; }
public required string Body { get; init; }
public List<string> Attachments { get; init; } = [];
}
// Compiler error if you forget To, Subject, or Body
var email = new EmailMessage
{
To = "julian@krause.dev",
Subject = "Design Patterns",
Body = "Modern C# changes everything."
};
Discriminated Unions with Pattern Matching
Where you once needed the Visitor pattern to safely handle type hierarchies, C# pattern matching with exhaustive switch expressions provides the same guarantees with far less code.
public abstract record PaymentResult
{
public record Success(string TransactionId, decimal Amount) : PaymentResult;
public record Declined(string Reason) : PaymentResult;
public record Error(Exception Exception) : PaymentResult;
}
public string HandlePayment(PaymentResult result) => result switch
{
PaymentResult.Success s => $"Payment {s.TransactionId} completed: {s.Amount:C}",
PaymentResult.Declined d => $"Payment declined: {d.Reason}",
PaymentResult.Error e => $"Payment error: {e.Exception.Message}",
_ => throw new UnreachableException()
};
Singleton with DI Container
The DI container in .NET makes manual Singleton implementations an anti-pattern. You get thread safety, lifecycle management, and testability for free.
// Never do this in modern .NET
public class LegacySingleton
{
private static readonly Lazy<LegacySingleton> _instance = new(() => new());
public static LegacySingleton Instance => _instance.Value;
private LegacySingleton() { }
}
// Just register as singleton
builder.Services.AddSingleton<ICacheService, RedisCacheService>();
The meta-lesson is this: patterns are solutions to problems, not goals in themselves. If a language feature solves the same problem with less code and more safety, use the language feature.

Comments (3)
I have a question: does this also apply to older versions?
I agree, great article!
Exactly! I had the same thought.
Thanks for sharing — very helpful.
Could you elaborate on this topic in a follow-up post?
Leave a comment