SOLID Principles with C#
Source: Dev.to
In software development, the SOLID principles—defined by Robert C. Martin—are five fundamental guidelines that help create flexible, readable, and maintainable object‑oriented systems. In C#, these principles are commonly used as rules for class design.
Single Responsibility Principle (SRP)
A class should have only one responsibility, i.e., only one reason to change. When a class performs multiple tasks (e.g., saving data and generating reports), it becomes harder to maintain and more error‑prone.
// Wrong: Handles both saving orders and printing invoices
public class OrderManager
{
public void SaveOrder(Order order) { /* save */ }
public void PrintInvoice(Order order) { /* print */ }
}
// Correct: Each class has a single responsibility
public class OrderRepository
{
public void Save(Order order) { /* save */ }
}
public class InvoicePrinter
{
public void Print(Order order) { /* print */ }
}Open/Closed Principle (OCP)
Classes should be open for extension but closed for modification. New behavior should be added without changing existing class code, typically by using abstract classes or interfaces.
// Wrong: Adding a new payment method requires modifying the class
public class PaymentService
{
public void Pay(string type)
{
if (type == "CreditCard") { /* ... */ }
else if (type == "PayPal") { /* ... */ }
}
}
// Correct: Adding a new method requires creating a new class
public interface IPayment
{
void Pay();
}
public class CreditCardPayment : IPayment
{
public void Pay() { /* ... */ }
}
public class PayPalPayment : IPayment
{
public void Pay() { /* ... */ }
}Liskov Substitution Principle (LSP)
Derived classes must be substitutable for their base classes. A subclass should work correctly wherever its base class is expected and must not break the base class’s contract.
public abstract class Bird
{
public abstract void Fly();
}
public class Sparrow : Bird
{
public override void Fly() => Console.WriteLine("Sparrow is flying");
}
// Wrong: Penguins cannot fly, but Fly() is forced
public class Penguin : Bird
{
public override void Fly() => throw new NotImplementedException();
}The Penguin class violates LSP because the Bird abstraction assumes that all birds can fly. A better design separates flying and non‑flying birds into distinct interfaces, e.g., IFlyingBird and INonFlyingBird.
Interface Segregation Principle (ISP)
Clients should not be forced to depend on interfaces they do not use. Prefer small, focused interfaces over large, “fat” ones.
// Wrong: All animals are forced to walk, fly, and swim
public interface IAnimal
{
void Walk();
void Fly();
void Swim();
}
// Correct: Split into smaller interfaces
public interface IWalkable { void Walk(); }
public interface IFlyable { void Fly(); }
public interface ISwimmable { void Swim(); }
public class Dog : IWalkable, ISwimmable
{
public void Walk() { /* ... */ }
public void Swim() { /* ... */ }
}Dependency Inversion Principle (DIP)
High‑level modules should not depend on low‑level modules; both should depend on abstractions (interfaces or abstract classes). This principle underlies Dependency Injection.
// Wrong: Service depends directly on a concrete logger
public class Service
{
private readonly ConsoleLogger _logger = new ConsoleLogger();
}
// Correct: Service depends on an abstraction
public interface ILogger
{
void Log(string msg);
}
public class ConsoleLogger : ILogger
{
public void Log(string msg) => Console.WriteLine(msg);
}
public class Service
{
private readonly ILogger _logger;
public Service(ILogger logger) => _logger = logger;
}Benefits of Applying SOLID
- Improves code readability and maintainability
- Creates an architecture more resistant to change
- Simplifies unit testing
- Reduces code duplication and increases reusability
Summary of the principles
- SRP – A class should have only one responsibility.
- OCP – Open for extension, closed for modification.
- LSP – Subclasses must be substitutable for their base classes.
- ISP – Prefer small, focused interfaces.
- DIP – Depend on abstractions, not concrete implementations.