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

Developer FabioDeveloper Fabio
2 min read

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.

0
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.