MSDN Documentation

C# Design Patterns

Explore fundamental design patterns implemented in C# to build robust, maintainable, and scalable applications. This documentation provides a comprehensive overview, examples, and best practices for leveraging these powerful object-oriented solutions.

Creational Patterns

These patterns are concerned with object creation mechanisms, trying to create objects in a manner suitable to the situation. They increase flexibility and reusability of code.

Singleton Pattern

Ensures a class only has one instance and provides a global point of access to it. Useful for managing shared resources like logging or configuration.


public sealed class Singleton
{
    private static readonly Singleton instance = new Singleton();

    private Singleton() { }

    public static Singleton Instance
    {
        get { return instance; }
    }

    public void DoSomething()
    {
        Console.WriteLine("Singleton is doing something.");
    }
}
                    

Factory Method Pattern

Defines an interface for creating an object, but lets subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses.


public abstract class Creator
{
    public abstract IProduct FactoryMethod();

    public void AnOperation()
    {
        var product = FactoryMethod();
        Console.WriteLine($"Creator: The same creator's code has just worked with {product.Operation()}");
    }
}

public interface IProduct { }
public interface IProduct_Operation { }
public interface IProduct_AnotherOperation { }

public class ConcreteCreatorA : Creator
{
    public override IProduct FactoryMethod()
    {
        return new ConcreteProductA();
    }
}

public class ConcreteProductA : IProduct { }
                    

Structural Patterns

These patterns are concerned with class and object composition. They explain how to assemble objects and classes into larger structures or how to compose them to get new functionality.

Adapter Pattern

Allows objects with incompatible interfaces to collaborate. It converts the interface of a class into another interface clients expect.


public interface ITarget
{
    void Request();
}

public class Adapter : ITarget
{
    private readonly Adaptee _adaptee;

    public Adapter(Adaptee adaptee)
    {
        _adaptee = adaptee;
    }

    public void Request()
    {
        Console.WriteLine("Adapter: Adapting ...");
        _adaptee.SpecificRequest();
    }
}

public class Adaptee
{
    public void SpecificRequest()
    {
        Console.WriteLine("Adaptee: Specific Request.");
    }
}
                    

Decorator Pattern

Attaches additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.


public abstract class Decorator : Component
{
    protected Component _component;

    public Decorator(Component component)
    {
        _component = component;
    }

    public override string Operation()
    {
        return _component.Operation();
    }
}

public abstract class Component
{
    public abstract string Operation();
}

public class ConcreteComponent : Component
{
    public override string Operation()
    {
        return "ConcreteComponent";
    }
}

public class ConcreteDecoratorA : Decorator
{
    public ConcreteDecoratorA(Component component) : base(component) { }

    public override string Operation()
    {
        return $"ConcreteDecoratorA({base.Operation()})";
    }
}
                    

Behavioral Patterns

These patterns are concerned with algorithms and the assignment of responsibilities between objects. They characterize complex control flow that arises during program execution.

Strategy Pattern

Defines a family of algorithms, encapsulates each one, and makes them interchangeable. Strategy lets the algorithm vary independently from clients that use it.


public interface IStrategy
{
    string Execute(string data);
}

public class ConcreteStrategyA : IStrategy
{
    public string Execute(string data)
    {
        return "ConcreteStrategyA: " + data;
    }
}

public class Context
{
    private IStrategy _strategy;

    public Context(IStrategy strategy)
    {
        _strategy = strategy;
    }

    public void SetStrategy(IStrategy strategy)
    {
        _strategy = strategy;
    }

    public string ExecuteStrategy(string data)
    {
        return _strategy.Execute(data);
    }
}
                    

Observer Pattern

Defines a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically. Commonly used for event handling.


public class Subject
{
    private List _observers = new List();
    public int State { get; set; }

    public void Attach(IObserver observer)
    {
        _observers.Add(observer);
    }

    public void Detach(IObserver observer)
    {
        _observers.Remove(observer);
    }

    public void Notify()
    {
        foreach (var observer in _observers)
        {
            observer.Update(this);
        }
    }
}

public interface IObserver
{
    void Update(Subject subject);
}

public class ConcreteObserverA : IObserver
{
    public void Update(Subject subject)
    {
        Console.WriteLine($"ConcreteObserverA: Subject's state changed to {subject.State}.");
    }
}
                    

Concurrency Patterns

Patterns specifically designed to manage concurrent operations, threading, and synchronization in multi-threaded applications.

Producer-Consumer Pattern

A classic concurrency pattern where one or more producers create data and one or more consumers process that data, typically using a shared buffer.

This pattern helps decouple producers and consumers, allowing them to operate at different rates and improving overall system throughput.


// Simplified conceptual example
public class SharedBuffer
{
    private Queue _buffer = new Queue();
    private int _capacity;
    private object _lock = new object();
    private SemaphoreSlim _producerSemaphore;
    private SemaphoreSlim _consumerSemaphore;

    public SharedBuffer(int capacity)
    {
        _capacity = capacity;
        _producerSemaphore = new SemaphoreSlim(capacity, capacity);
        _consumerSemaphore = new SemaphoreSlim(0, capacity);
    }

    public void Produce(T item)
    {
        _producerSemaphore.Wait();
        lock (_lock)
        {
            _buffer.Enqueue(item);
        }
        _consumerSemaphore.Release();
    }

    public T Consume()
    {
        _consumerSemaphore.Wait();
        T item;
        lock (_lock)
        {
            item = _buffer.Dequeue();
        }
        _producerSemaphore.Release();
        return item;
    }
}