Loading...

SOLID Principles with C#

Applying SOLID principles in C# with examples: building flexible, maintainable, and testable code.

In software development, the SOLID principles, defined by Robert C. Martin, are five fundamental guidelines aimed at creating more flexible, readable, and maintainable systems in object-oriented programming (OOP). In C#, these principles are widely applied as guiding rules in class design.


1. Single Responsibility Principle (SRP)

A class should have only one responsibility. In other words, it should have only one reason to change. If a class performs multiple tasks (e.g., saving data + generating reports), it becomes harder to maintain and more prone to errors.


// 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 */ }
}

2. Open/Closed Principle (OCP)

Classes should be open for extension, but closed for modification. That means new behavior should be added without changing existing class code. This is usually achieved 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() { /* ... */ }
}

3. Liskov Substitution Principle (LSP)

Derived classes must be substitutable for their base classes. In other words, a subclass should work correctly wherever its base class is expected. A subclass must not break the contract of its base class.


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(); }
}

Here, the Penguin class violates LSP. Because the Bird abstract class imposes the assumption that "all birds can fly". Instead, separate interfaces like IFlyingBird and INonFlyingBird should be defined.


4. Interface Segregation Principle (ISP)

A class should not be forced to implement large interfaces containing methods it does not need. Instead, smaller, more focused interfaces should be preferred.


// 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() { /* ... */ }
}

5. Dependency Inversion Principle (DIP)

High-level classes should not depend directly on low-level classes. Both should depend on abstractions (interfaces or abstract classes). This principle forms the basis of Dependency Injection.


// Wrong: Service depends directly on ConsoleLogger
public class Service
{
    private readonly ConsoleLogger _logger = new ConsoleLogger();
}

// Correct: Depends on 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; }
}

Advantages


TL;DR

  • SRP: A class should have only one responsibility.
  • OCP: Open for extension, closed for modification.
  • LSP: Subclasses should be substitutable for base classes.
  • ISP: Prefer small, focused interfaces over large ones.
  • DIP: Depend on abstractions, not concrete classes.

Related Articles