SOLID Principles in C#
SOLID is one of those topics that shows up in every senior .NET interview — and for good reason. These five principles help you write cleaner, more maintainable, and more scalable code. But the real value comes from understanding how to apply them in real-world .NET systems.
This guide breaks down each principle with simple definitions, C# examples, and practical scenarios you can use in interviews or production code.
S — Single Responsibility Principle (SRP)
A class should have one and only one reason to change.
What it means
A class should do one thing.
Not “mostly one thing.”
Not “one thing plus a helper.”
Just one responsibility.
Bad example
public class OrderService
{
public void CreateOrder(Order order) { }
public void SendEmail(Order order) { }
}
This class handles business logic and emailing — two responsibilities.
Good example
public class OrderService
{
public void CreateOrder(Order order) { }
}
public class EmailService
{
public void SendEmail(Order order) { }
}
Real‑world scenario
- Logging mixed with business logic
- Controllers doing validation + mapping + business logic
- Services doing too much
SRP makes your code testable and easier to maintain.
O — Open/Closed Principle (OCP)
Software entities should be open for extension but closed for modification.
What it means
You should be able to add new behavior without changing existing code.
Bad example
public class PaymentProcessor
{
public void Pay(string method)
{
if (method == "CreditCard") { }
else if (method == "PayPal") { }
else if (method == "Crypto") { }
}
}
Every new payment method requires modifying this class.
Good example
public interface IPaymentMethod
{
void Pay();
}
public class CreditCard : IPaymentMethod { public void Pay() { } }
public class PayPal : IPaymentMethod { public void Pay() { } }
public class PaymentProcessor
{
public void Pay(IPaymentMethod method) => method.Pay();
}
Real‑world scenario
Adding new features without breaking existing ones — especially in enterprise systems.
L — Liskov Substitution Principle (LSP)
Subclasses should be substitutable for their base classes.
What it means
If class B inherits from class A, you should be able to use B anywhere A is expected — without breaking behavior.
Bad example
public class Bird
{
public virtual void Fly() { }
}
public class Penguin : Bird
{
public override void Fly() => throw new Exception("Penguins can't fly");
}
This violates LSP — a Penguin is not a Bird in this model.
Good example
public abstract class Bird { }
public class FlyingBird : Bird { public void Fly() { } }
public class Penguin : Bird { }
Real‑world scenario
Inheritance hierarchies that break behavior or introduce exceptions.
I — Interface Segregation Principle (ISP)
Clients should not be forced to depend on interfaces they do not use.
What it means
Small, focused interfaces are better than large, bloated ones.
Bad example
public interface IWorker
{
void Work();
void Eat();
}
Robots don’t eat.
Good example
public interface IWorkable { void Work(); }
public interface IFeedable { void Eat(); }
Real‑world scenario
Large service interfaces with 20+ methods — most implementations only use a few.
D — Dependency Inversion Principle (DIP)
Depend on abstractions, not concrete implementations.
What it means
High-level modules should not depend on low-level modules.
Both should depend on interfaces.
Bad example
public class ReportService
{
private readonly FileLogger _logger = new FileLogger();
}
Tightly coupled.
Good example
public class ReportService
{
private readonly ILogger _logger;
public ReportService(ILogger logger) => _logger = logger;
}
Now you can inject:
- FileLogger
- DatabaseLogger
- CloudLogger
Real‑world scenario
Dependency Injection in ASP.NET Core is built entirely on DIP.
Putting SOLID Together — A Real .NET Example
Imagine building an order processing system:
- SRP → OrderService handles orders only
- OCP → Add new payment methods without modifying existing code
- LSP → Subclasses behave correctly when substituted
- ISP → Interfaces are small and focused
- DIP → Services depend on abstractions, not concrete classes
This leads to cleaner architecture, easier testing, and safer refactoring.
Interview‑Ready Summary
- SRP → One responsibility
- OCP → Extend, don’t modify
- LSP → Subtypes must behave correctly
- ISP → Small interfaces
- DIP → Depend on abstractions
A strong interview answer:
“SOLID helps create maintainable, scalable, and testable systems. It reduces coupling, improves extensibility, and keeps code clean as the system grows.”
⭐ Add‑On
Where Did SOLID Come From? A Brief Origin Story
SOLID didn’t appear out of nowhere — it has a clear lineage in software engineering history.
The principles were originally introduced by Robert C. Martin (Uncle Bob), one of the most influential figures in modern software craftsmanship. He coined the acronym SOLID in the early 2000s to group together five existing object‑oriented design principles that had been discussed in academic and professional circles for years.
The ideas themselves were heavily inspired by earlier work from:
- Bertrand Meyer (creator of Eiffel) — especially the Open/Closed Principle
- Barbara Liskov — whose Liskov Substitution Principle became the “L” in SOLID
- Tom DeMarco, Grady Booch, and others — pioneers of structured and OO design
SOLID gained massive popularity because:
- Uncle Bob promoted it through books, talks, and the Agile movement
- It aligned perfectly with Test‑Driven Development (TDD)
- It helped teams build maintainable, scalable systems
- It became a cornerstone of Clean Architecture and modern .NET design
Today, SOLID is considered foundational knowledge for senior developers, architects, and anyone building long‑lived enterprise systems.
Top comments (0)