SOLID Principles in C#: Theory and Practice from A to Z

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 to SOLID Principles in C#

\n

Developing in C# within the .NET ecosystem requires not only knowledge of syntax but also the ability to design flexible, maintainable, and testable systems. This is precisely why the SOLID principles were created — five fundamental rules of object-oriented programming formulated by Robert C. Martin (Uncle Bob).

\n

The SOLID acronym stands for:

\n
    \n
  • S — Single Responsibility Principle
  • \n
  • O — Open/Closed Principle
  • \n
  • L — Liskov Substitution Principle
  • \n
  • I — Interface Segregation Principle
  • \n
  • D — Dependency Inversion Principle
  • \n
\n

In this article, we will break down each principle in practice with examples in C#, show how violating them leads to problems, and how proper application makes code cleaner.

\n\n

1. Single Responsibility Principle (SRP)

\n

Essence: A class should have only one reason to change. In other words, each class is responsible for one clearly defined task.

\n\n

Violation of SRP in C#

\n

Consider a typical example — a class OrderService that does too much:

\n
public class OrderService\n{\n    public void ProcessOrder(Order order)\n    {\n        // 1. Save to DB\n        SaveToDatabase(order);\n        \n        // 2. Send email\n        SendEmail(order.CustomerEmail, "Order placed");\n        \n        // 3. Logging\n        File.WriteAllText("log.txt", $"Order {order.Id} processed");\n    }\n    \n    private void SaveToDatabase(Order order) { /* ... */ }\n    private void SendEmail(string email, string message) { /* ... */ }\n}
\n

Problem: Changing the email sending logic or the logging method will lead to changes in the OrderService class. This violates SRP.

\n\n

Correct SRP Implementation

\n

Separate responsibilities into individual services:

\n
public class OrderProcessor\n{\n    private readonly IOrderRepository _repository;\n    private readonly INotificationService _notificationService;\n    private readonly ILogger _logger;\n    \n    public OrderProcessor(\n        IOrderRepository repository,\n        INotificationService notificationService,\n        ILogger logger)\n    {\n        _repository = repository;\n        _notificationService = notificationService;\n        _logger = logger;\n    }\n    \n    public void ProcessOrder(Order order)\n    {\n        _repository.Save(order);\n        _notificationService.SendConfirmation(order.CustomerEmail);\n        _logger.Log($"Order {order.Id} processed");\n    }\n}\n\npublic interface IOrderRepository { void Save(Order order); }\npublic interface INotificationService { void SendConfirmation(string email); }\npublic interface ILogger { void Log(string message); }
\n

Now each class is responsible only for its own task. Changing the logging logic does not affect order processing.

\n\n

2. Open/Closed Principle (OCP)

\n

Essence: Classes should be open for extension but closed for modification. That is, you should be able to add new behavior without changing existing code.

\n\n

Violation of OCP in C#

\n

A classic example — a calculator with conditions:

\n
public class AreaCalculator\n{\n    public double CalculateArea(object shape)\n    {\n        if (shape is Rectangle rect)\n            return rect.Width * rect.Height;\n        else if (shape is Circle circle)\n            return Math.PI * circle.Radius * circle.Radius;\n        // Adding a new shape requires changing this method!\n        throw new NotSupportedException();\n    }\n}                

Blogs

Book Recommendations