SOLID in C#: The Fundamental Principles for Clean and Maintainable Code


We’ve already seen the Single Responsibility Principle (SRP). Today, let’s explore the other four pillars of clean code according to SOLID:
O: Open/Closed Principle
L: Liskov Substitution Principle
I: Interface Segregation Principle
D: Dependency Inversion Principle
Open/Closed Principle (OCP)
“Software entities (classes, modules, functions, etc.) should be open for extension but closed for modification.”
In practice:
You can add new functionality without modifying existing code, thus avoiding regressions.
Example:
public abstract class Discount
{
public abstract decimal Apply(decimal price);
}
public class SeasonalDiscount : Discount
{
public override decimal Apply(decimal price) => price * 0.9m;
}
public class ClearanceDiscount : Discount
{
public override decimal Apply(decimal price) => price * 0.7m;
}
You can add new types of discounts by extending Discount
without changing the client code.
Liskov Substitution Principle (LSP)
“Objects of a derived class must be replaceable with objects of the base class without altering the behavior of the program.”
Why it matters:
If a subclass doesn’t respect the expected behavior, it breaks code that relies on polymorphism.
Wrong example:
public class Bird
{
public virtual void Fly() { /* flies */ }
}
public class Ostrich : Bird
{
public override void Fly()
{
throw new NotSupportedException();
}
}
Ostrich
doesn't fly — it violates LSP.
Interface Segregation Principle (ISP)
“Many specific interfaces are better than one general-purpose interface.”
In practice:
Don’t force classes to implement methods they don’t need.
Example:
public interface IPrinter
{
void Print(Document doc);
}
public interface IScanner
{
void Scan(Document doc);
}
public class MultiFunctionPrinter : IPrinter, IScanner
{
public void Print(Document doc) { /* print */ }
public void Scan(Document doc) { /* scan */ }
}
public class SimplePrinter : IPrinter
{
public void Print(Document doc) { /* print */ }
}
Dependency Inversion Principle (DIP)
“High-level modules should not depend on low-level modules. Both should depend on abstractions.”
Example:
public interface IMessageSender
{
void Send(string message);
}
public class EmailSender : IMessageSender
{
public void Send(string message) { /* send email */ }
}
public class NotificationService
{
private readonly IMessageSender _sender;
public NotificationService(IMessageSender sender)
{
_sender = sender;
}
public void Notify(string msg)
{
_sender.Send(msg);
}
}
Why SOLID?
Improves maintainability
Encourages reuse
Enables easy testing
Reduces complexity and coupling
Conclusion
SOLID isn’t just theory—applying these principles in your C# code helps you write more robust and easily evolvable software.
In the next article, we’ll wrap up the series with practical tips for writing clean REST APIs in ASP.NET Core.
Subscribe to my newsletter
Read articles from Developer Fabio directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Developer Fabio
Developer Fabio
I'm a fullstack developer and my stack is includes .net, angular, reactjs, mondodb and mssql I currently work in a little tourism company, I'm not only a developer but I manage a team and customers. I love learning new things and I like the continuous comparison with other people on ideas.