Design Patterns in C#: Complete Guide with Examples
Design Patterns are proven solutions to typical problems that arise during software development. In the .NET ecosystem and C#, knowledge of patterns is especially important, as many of them are built directly into the platform (for example, IDisposable for the Dispose pattern). In this article, we will cover three main groups of patterns according to the GoF (Gang of Four) classification with practical examples in C#.
Creational Patterns
Creational patterns are responsible for object creation, making the system independent of how objects are created, composed, and represented.
Singleton
Ensures that a class has only one instance and provides a global access point to it. In C#, it is often used for working with loggers, cache, or configuration.
public sealed class Logger{ private static readonly Lazy<Logger> _instance = new Lazy<Logger>(() => new Logger());
public static Logger Instance => _instance.Value;
private Logger() { }
public void Log(string message) { Console.WriteLine($"[{DateTime.Now}] {message}"); }}
// UsageLogger.Instance.Log("Application started");Factory Method
Defines an interface for creating an object but lets subclasses alter the type of created object. It is well-suited for logic that needs to be extensible.
public interface ITransport{ void Deliver();}
public class Truck : ITransport{ public void Deliver() => Console.WriteLine("Delivery by road");}
public class Ship : ITransport{ public void Deliver() => Console.WriteLine("Delivery by sea");}
public abstract class Logistics{ public abstract ITransport CreateTransport();
public void PlanDelivery() { var transport = CreateTransport(); transport.Deliver(); }}
public class RoadLogistics : Logistics{ public override ITransport CreateTransport() => new Truck();}
public class SeaLogistics : Logistics{ public override ITransport CreateTransport() => new Ship();}
// UsageLogistics logistics = new RoadLogistics();logistics.PlanDelivery(); // Delivery by roadStructural Patterns
Structural patterns define ways to compose classes and objects to form larger structures.
Adapter
Converts the interface of one class into another interface expected by the client. It is often used for integration with third-party libraries or legacy code.
// Target interface (ITarget)public interface IXmlParser{ string ParseToXml(string data);}
// Adaptee classpublic class JsonParser{ public string ParseToJson(string data) { return $"{{\\"data\\": \\"{data}\\"}}"; }}
// Adapterpublic class JsonToXmlAdapter : IXmlParser{ private readonly JsonParser _jsonParser;
public JsonToXmlAdapter(JsonParser jsonParser) { _jsonParser = jsonParser; }
public string ParseToXml(string data) { var json = _jsonParser.ParseToJson(data); // Simplest JSON to XML conversion return $"<data>{data}</data>"; }}
// Usagevar adapter = new JsonToXmlAdapter(new JsonParser());string xml = adapter.ParseToXml("Hello");Console.WriteLine(xml); // <data>Hello</data>Decorator
Dynamically adds new responsibilities to an object. In C#, this pattern is often implemented through inheritance or composition.
public interface INotifier{ void Send(string message);}
public class E