C# Design Patterns

C# Design Patterns with Examples (GoF) | Complete Guide for .NET

Design patterns play a critical role in modern C# application development. As software systems grow in size and complexity, developers must deal with recurring design problems related to object creation, system structure, communication between components, scalability, and maintainability. Design patterns provide proven, time-tested solutions to these challenges.

In C#, design patterns are widely used in enterprise applications, web APIs, microservices, desktop software, cloud-native systems, and framework design. Understanding design patterns is not only important for writing better code but also essential for system design discussions, technical interviews, and long-term project success.

This guide explains C# design patterns from fundamentals to advanced usage. It focuses on practical understanding rather than theory, helping developers choose the right pattern for the right problem without overengineering.


What Is a Design Pattern?

A design pattern is a reusable solution to a commonly occurring problem in software design. It is not a piece of code that can be copied and pasted directly into an application. Instead, it is a general blueprint that can be adapted to solve a particular problem within a given context.

Design patterns describe relationships between classes and objects, responsibilities of components, and collaboration strategies that improve code quality. They help developers avoid reinventing the wheel and reduce design flaws that typically appear in large applications.

In C#, design patterns leverage object-oriented concepts such as abstraction, inheritance, encapsulation, and polymorphism to achieve flexible and maintainable designs.


Why Design Patterns Matter in C# Applications

C# is commonly used to build enterprise-grade systems where requirements evolve continuously. Without proper design, applications become tightly coupled, fragile, and difficult to modify. Design patterns address this problem by promoting clean separation of concerns.

Benefits of using design patterns in C# include:

  • Improved code readability and consistency
  • Better testability and easier unit testing
  • Reduced coupling between components
  • Clear responsibility assignment
  • Improved scalability and extensibility
  • Faster onboarding of new developers

Most modern .NET frameworks internally rely on design patterns. For example, ASP.NET Core heavily uses dependency injection, middleware pipelines, factories, and strategy-based configurations.


Design Patterns vs Design Principles

Design principles and design patterns are often confused, but they serve different purposes. Design principles are high-level guidelines that influence how software should be structured. Examples include SOLID, DRY, and KISS principles.

Design patterns, on the other hand, are concrete solutions that implement these principles in practice. For instance, the Dependency Inversion Principle is often implemented using the Dependency Injection pattern.

In practice, principles guide decision-making, while patterns provide ready-to-use architectural solutions.


How to Use Design Patterns Effectively

Design patterns should not be applied blindly. Using patterns without understanding the problem often leads to unnecessary complexity. A good design starts with simple code and evolves as requirements grow.

When deciding whether to use a design pattern:

  • Identify the recurring problem in your system
  • Evaluate whether the pattern solves that problem clearly
  • Consider trade-offs such as complexity and performance
  • Avoid premature optimization or overengineering

In many real-world systems, multiple design patterns work together. For example, a factory may use a strategy internally, or a facade may rely on adapters behind the scenes.


Gang of Four (GoF) Design Patterns

The most widely known design patterns are the 23 Gang of Four (GoF) patterns. These patterns were introduced in the book Design Patterns: Elements of Reusable Object-Oriented Software published in 1994.

Types of Design Patterns

Design patterns are categorized into three groups: Creational, Structural, and Behavioral. 

Creational Design Patterns

Creational design patterns solve the problems related to object creation. They help to abstract away object creation processes that spread across multiple classes.

The following five patterns are creational design patterns:

  1. Singleton
  2. Abstract Factory
  3. Builder
  4. Factory Method
  5. Prototype

Structural Design Patterns

The structural design patterns suggest implementing relationships between classes and objects.

The following seven patterns are structural design patterns:

  1. Adapter
  2. Bridge
  3. Composite
  4. Decorator
  5. Facade
  6. Flyweight
  7. Proxy

Behavioral Design Pattern

The behavioral design patterns suggest ways of communication between classes and objects.

The following twelve patterns are behavioral design patterns:

  1. Chain of Responsibility
  2. Command
  3. Interpreter
  4. Iterator
  5. Mediator
  6. Memento
  7. Observer
  8. State
  9. Strategy
  10. Template Method
  11. Visitor
  12. Mediator

Creational Design Patterns

Creational design patterns deal with object creation mechanisms. They abstract the instantiation process and make systems independent of how objects are created and composed.

1. Singleton Pattern

The Singleton pattern ensures that a class has only one instance and provides a global access point to that instance. In C#, it is commonly used for configuration managers, logging services, and caching layers.

While useful, excessive use of singletons can lead to hidden dependencies and reduced testability. Modern C# applications often replace singletons with dependency injection containers.

Use case: Configuration, logging, caching, app-wide state

Singleton Pattern – C# Example


public sealed class AppConfig
{
    private static readonly Lazy<AppConfig> _instance =
        new Lazy<AppConfig>(() => new AppConfig());

    public static AppConfig Instance => _instance.Value;

    public string ApplicationName { get; private set; }

    private AppConfig()
    {
        ApplicationName = "My C# Application";
    }
}

Use Singleton carefully. Overuse can lead to hidden dependencies and difficulties in unit testing.


2. Factory Method Pattern

The Factory Method pattern defines an interface for creating objects but allows subclasses to decide which class to instantiate. This pattern promotes loose coupling by eliminating direct object creation.

In C#, factory methods are frequently used when object creation depends on configuration, runtime data, or environment-specific logic.

Use case: Object creation based on condition or configuration

Factory Method Pattern – C# Example


public interface INotification
{
    void Send(string message);
}

public class EmailNotification : INotification
{
    public void Send(string message)
    {
        Console.WriteLine("Email sent: " + message);
    }
}

public class SmsNotification : INotification
{
    public void Send(string message)
    {
        Console.WriteLine("SMS sent: " + message);
    }
}

public static class NotificationFactory
{
    public static INotification Create(string type)
    {
        return type switch
        {
            "email" => new EmailNotification(),
            "sms" => new SmsNotification(),
            _ => throw new ArgumentException("Invalid type")
        };
    }
}

3. Abstract Factory Pattern

Abstract Factory provides an interface for creating families of related objects without specifying their concrete classes. It is useful when systems need to support multiple product variants.

This pattern is commonly found in UI frameworks, database providers, and cross-platform development scenarios.

Use case: Multiple related object families

Abstract Factory Pattern – C# Example


public interface IButton
{
    void Render();
}

public class WindowsButton : IButton
{
    public void Render() => Console.WriteLine("Windows Button");
}

public class MacButton : IButton
{
    public void Render() => Console.WriteLine("Mac Button");
}

public interface IUIFactory
{
    IButton CreateButton();
}

public class WindowsFactory : IUIFactory
{
    public IButton CreateButton() => new WindowsButton();
}

public class MacFactory : IUIFactory
{
    public IButton CreateButton() => new MacButton();
}

4. Builder Pattern

The Builder pattern separates the construction of a complex object from its representation. It is ideal for objects with many optional parameters.

In C#, the Builder pattern improves readability and avoids long constructor parameter lists.

Use case: Complex object construction

Builder Pattern – C# Example


public class User
{
    public string Name { get; set; }
    public string Email { get; set; }
    public bool IsAdmin { get; set; }
}

public class UserBuilder
{
    private readonly User _user = new User();

    public UserBuilder SetName(string name)
    {
        _user.Name = name;
        return this;
    }

    public UserBuilder SetEmail(string email)
    {
        _user.Email = email;
        return this;
    }

    public UserBuilder SetAdmin(bool isAdmin)
    {
        _user.IsAdmin = isAdmin;
        return this;
    }

    public User Build() => _user;
}

5. Prototype Pattern

The Prototype pattern creates new objects by copying existing ones. This approach improves performance when object creation is expensive.

Use case: Expensive object creation

Prototype Pattern – C# Example


public class Document : ICloneable
{
    public string Title { get; set; }

    public object Clone()
    {
        return MemberwiseClone();
    }
}

Structural Design Patterns

Structural design patterns focus on how classes and objects are composed to form larger structures while keeping them flexible and efficient.

1. Adapter Pattern

Adapter converts the interface of one class into another interface expected by clients. It allows incompatible classes to work together.

In C#, adapters are frequently used when integrating third-party libraries or legacy systems.

Use case: Integrating legacy or third-party APIs

Adapter Pattern – C# Example


public class LegacyLogger
{
    public void WriteLog(string text)
    {
        Console.WriteLine(text);
    }
}

public interface ILogger
{
    void Log(string message);
}

public class LoggerAdapter : ILogger
{
    private readonly LegacyLogger _logger = new LegacyLogger();

    public void Log(string message)
    {
        _logger.WriteLog(message);
    }
}

2. Facade Pattern

Facade provides a simplified interface to a complex subsystem. It improves usability and reduces coupling between layers.

Many service layers and API gateways in C# applications act as facades.

Use case: Simplifying complex subsystems

Facade Pattern – C# Example


public class OrderFacade
{
    public void PlaceOrder()
    {
        Console.WriteLine("Validating order");
        Console.WriteLine("Processing payment");
        Console.WriteLine("Shipping order");
    }
}

3. Decorator Pattern

Decorator allows behavior to be added to an object dynamically without modifying its structure. It is an alternative to inheritance.

Use case: Add behavior dynamically

Decorator Pattern – C# Example


public interface IMessage
{
    string GetContent();
}

public class SimpleMessage : IMessage
{
    public string GetContent() => "Hello";
}

public class EncryptedMessage : IMessage
{
    private readonly IMessage _message;

    public EncryptedMessage(IMessage message)
    {
        _message = message;
    }

    public string GetContent()
    {
        return "Encrypted(" + _message.GetContent() + ")";
    }
}

4. Composite Pattern

Composite treats individual objects and compositions uniformly. It is widely used in tree structures such as menus and file systems.

5. Proxy Pattern

Proxy provides a placeholder for another object to control access, add logging, caching, or security.

Use case: Lazy loading, security, logging

Proxy Pattern – C# Example


public interface IService
{
    void Execute();
}

public class RealService : IService
{
    public void Execute()
    {
        Console.WriteLine("Executing service");
    }
}

public class ServiceProxy : IService
{
    private RealService _service;

    public void Execute()
    {
        if (_service == null)
            _service = new RealService();

        Console.WriteLine("Logging before execution");
        _service.Execute();
    }
}

Behavioral Design Patterns

Behavioral patterns focus on communication between objects and assignment of responsibilities.

1. Observer Pattern

Observer defines a one-to-many dependency between objects so that when one object changes state, all dependents are notified.

Events and delegates in C# naturally implement the Observer pattern.

Use case: Events, notifications

Observer Pattern – C# Example


public class NewsPublisher
{
    public event Action<string> NewsPublished;

    public void Publish(string news)
    {
        NewsPublished?.Invoke(news);
    }
}

2. Strategy Pattern

Strategy defines a family of algorithms and makes them interchangeable. It allows behavior to be selected at runtime.

Use case: Runtime behavior selection

Strategy Pattern – C# Example


public interface IPaymentStrategy
{
    void Pay(decimal amount);
}

public class CreditCardPayment : IPaymentStrategy
{
    public void Pay(decimal amount)
    {
        Console.WriteLine("Paid using credit card");
    }
}

public class PaymentContext
{
    private IPaymentStrategy _strategy;

    public PaymentContext(IPaymentStrategy strategy)
    {
        _strategy = strategy;
    }

    public void Execute(decimal amount)
    {
        _strategy.Pay(amount);
    }
}

3. Command Pattern

Command encapsulates a request as an object, allowing parameterization, queuing, and undo operations.

Use case: Undo, queue, decoupled requests

Command Pattern – C# Example


public interface ICommand
{
    void Execute();
}

public class SaveCommand : ICommand
{
    public void Execute()
    {
        Console.WriteLine("Saving data");
    }
}

4. Template Method Pattern

Template Method defines the skeleton of an algorithm in a base class while allowing subclasses to override specific steps.

Template Method Pattern – C# Example


public abstract class DataProcessor
{
    public void Process()
    {
        Read();
        Validate();
        Save();
    }

    protected abstract void Read();
    protected abstract void Validate();
    protected abstract void Save();
}

5. State Pattern

State allows an object to alter its behavior when its internal state changes. It eliminates complex conditional logic.

State Pattern – C# Example


public interface IOrderState
{
    void Process();
}

public class ShippedState : IOrderState
{
    public void Process()
    {
        Console.WriteLine("Order shipped");
    }
}

6. Mediator Pattern

Mediator reduces coupling between objects by centralizing communication.


Common Mistakes When Using Design Patterns

  • Using patterns without understanding the problem
  • Overengineering simple solutions
  • Applying multiple patterns unnecessarily
  • Ignoring performance and maintainability trade-offs

The best developers use design patterns sparingly and intentionally. A pattern should simplify the system, not complicate it.


Design Patterns in Modern C# and .NET

Modern C# and .NET frameworks already implement many design patterns internally. Dependency injection containers, middleware pipelines, LINQ providers, and logging frameworks are all pattern-driven.

Understanding design patterns helps developers use these frameworks correctly and extend them safely.


Design patterns are an essential tool for building high-quality C# applications. They provide reusable solutions to common problems and help developers write clean, flexible, and maintainable code.

Rather than memorizing patterns, focus on understanding the problems they solve and the trade-offs they introduce. With experience, design patterns become a natural part of your development toolkit.

Sandip Mhaske

I’m a software developer exploring the depths of .NET, AWS, Angular, React, and digital entrepreneurship. Here, I decode complex problems, share insightful solutions, and navigate the evolving landscape of tech and finance.

Post a Comment

Previous Post Next Post