SOLID Principles in C#: Theory and Practice with Code Examples

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Introduction: Why SOLID is the Foundation of Quality C# Code



Developing in C# within the .NET ecosystem requires not only knowledge of syntax but also the ability to design flexible, maintainable, and testable applications. SOLID is an acronym for five fundamental principles of object-oriented design formulated by Robert Martin. They help developers avoid "rigid" dependencies, code duplication, and scalability issues.



In this article, we will break down each of the five SOLID principles as applied to C#: from theory to real code examples. You will learn how refactoring with SOLID in mind makes your code cleaner and project maintenance easier.



1. Single Responsibility Principle (SRP)



Theory

A class should have only one reason to change. In other words, each class is responsible for exactly one functionality or business logic. If a class does too much, it becomes difficult to test, modify, and reuse.



Practice: SRP violation

public class OrderProcessor{    public void ProcessOrder(Order order)    {        // 1. Order validation        if (order.Total <= 0) throw new Exception("Invalid order");                // 2. Save to database        using var db = new AppDbContext();        db.Orders.Add(order);        db.SaveChanges();                // 3. Send email notification        SmtpClient client = new SmtpClient();        client.Send("admin@shop.com", order.CustomerEmail, "Order confirmed", "Thank you!");    }}

Here, OrderProcessor violates SRP: it is responsible for validation, database operations, and sending emails. Changing the notification logic would require modifying this same class.



Refactoring with SRP

public class OrderValidator{    public bool Validate(Order order) => order.Total > 0;}

public class OrderRepository{ public void Save(Order order) { using var db = new AppDbContext(); db.Orders.Add(order); db.SaveChanges(); }}

public class EmailService{ public void SendConfirmation(string email) { SmtpClient client = new SmtpClient(); client.Send("admin@shop.com", email, "Order confirmed", "Thank you!"); }}

public class OrderProcessor{ private readonly OrderValidator _validator; private readonly OrderRepository _repository; private readonly EmailService _emailService;

public OrderProcessor(OrderValidator validator, OrderRepository repository, EmailService emailService) { _validator = validator; _repository = repository; _emailService = emailService; }

public void ProcessOrder(Order order) { if (!_validator.Validate(order)) throw new Exception("Invalid order"); _repository.Save(order); _emailService.SendConfirmation(order.CustomerEmail); }}

Now each class has a single area of responsibility. This simplifies testing and modification.



2. Open/Closed Principle (OCP)



Theory

Software entities (classes, modules, functions) should be open for extension but closed for modification. That is, you should be able to add new functionality without changing existing code.



Practice: OCP violation

public class ShapeDrawer{    public void DrawShape(object shape)    {        if (shape is Circle circle)            Console.WriteLine($"Drawing circle with radius {circle.Radius}");        else if (shape is Rectangle rect)            Console.WriteLine($"Drawing rectangle {rect.Width}x{rect.Height}");        // When adding a new shape type — this method must be modified!    }}


Refactoring with OCP using abstraction

public abstract c                

Blogs

Book Recommendations