SOLID Principles in C# - An Overview

WEBINAR: On-demand webcast

How to Boost Database Development Productivity on Linux, Docker, and Kubernetes with Microsoft SQL Server 2017 REGISTER >

SOLID is an acronym and stands for 5 important object oriented principles. The SOLID principles help in making the object oriented application source code robust, scalable, extensible and non-fragile. It is absolutely necessary when you have a layered architecture and they are bound to changes over the period of time. Following are the 5 principles.

1.Single Responsibility Principle

2.Open Closed Principle

3.Liskov Substitution Principle

4.Interface Segregation Principle

5.Dependency Inversion Principle

In this article I will take you through all three all five SOLID principles and also provide sample C# code to demonstrate them.

Single Responsibility Principle (SRP)

SRP states that every class should have a single responsibility, and that responsibility should be entirely encapsulated by the class. There should only be a single reason for making the change to a class.

It means that a class should not be loaded with multiple responsibilities and a single responsibility should not be scattered across multiple classes and mixed with other responsibilities. The reason is that the more changes requested in the future, the more changes the class needs to undergo.

Below is a code violating the SRP. In the sample code, SRP is violated by mixing the OpenGate and CloseGate responsibility with the core vehicle service functionality.

public class ServiceStation
{
    public void OpenGate()
    {
        //Open the gate if the time is later than 9 AM
    }
 
    public void DoService(Vehicle vehicle)
    {
        //Check if service station is opened and then
        //complete the vehicle service
    }
 
    public void CloseGate()
    {
        //Close the gate if the time has crossed 6PM
    }
}

The re-factored code sample is as follows. A new interface is created and the gate related utility methods are moved to a different class called ServiceStationUtility.

public class ServiceStation
{
    IGateUtility _gateUtility;
 
    public ServiceStation(IGateUtility gateUtility)
    {
        this._gateUtility = gateUtility;
    }
    public void OpenForService()
    {
        _gateUtility.OpenGate();
    }
 
    public void DoService()
    {
        //Check if service station is opened and then
        //complete the vehicle service
    }
 
    public void CloseForDay()
    {
        _gateUtility.CloseGate();
    }
}
 
public class ServiceStationUtility : IGateUtility
{
    public void OpenGate()
    {
        //Open the shop if the time is later than 9 AM
    }
 
    public void CloseGate()
    {
        //Close the shop if the time has crossed 6PM
    }
}
 
 
public interface IGateUtility
{
    void OpenGate();
    void CloseGate();
}

Open Closed Principle (OCP)

OCP states that software application source codes should be open for extension but should be closed for modification.

According to the OCP principle the code should be easily extensible but it should not need any changes to be done to the core implementations. Following is a C# source code violating OCP where a new car has to be added then it will require changes in the core function CalculateMileage.

public class MileageCalculator
{
    IEnumerable<Car> _cars;
    public MileageCalculator(IEnumerable<Car> cars) { this._cars = cars; }
 
    public void CalculateMileage()
    {
        foreach (var car in _cars)
        {
            if (car.Name == "Audi")
                Console.WriteLine("Mileage of the car {0} is {1}", car.Name, "10M");
            else if (car.Name == "Mercedes")
                Console.WriteLine("Mileage of the car {0} is {1}", car.Name, "20M");
        }
    }
}

The OCP violation can be fixed as shown below, using an interface and creating classes for each car there, by reducing making the CalculateMileage method more generic and extensible.

public class MileageCalculator
{
    IEnumerable<Car> _cars;
    public MileageCalculator(IEnumerable<Car> cars) { this._cars = cars; }
 
    public void CalculateMileage()
    {
        CarController controller = new CarController();
        foreach (var car in _cars)
        {
                Console.WriteLine("Mileage of the car {0} is {1}", car.Name, controller.GetCarMileage(car.Name));
        }
    }
}
 
public class CarController
{
    List<ICar> cars; 
    public CarController()
    {
        cars = new List<ICar>();
        cars.Add(new Audi());
        cars.Add(new Mercedes());
    }
 
    public string GetCarMileage(string name)
    {
        return cars.First(car => car.Name == name).GetMileage();
    }
}
 
public interface ICar 
{ 
    string Name { get; set; }
    string GetMileage();
}
 
public class Audi : ICar
{
    public string Name { get; set; }
 
    public string GetMileage()
    {
        return "10M";
    }
}
 
public class Mercedes : ICar
{
    public string Name { get; set; }
 
    public string GetMileage()
    {
        return "20M";
    }
}

Liskov Substitution Principle (LSP)

LSP states that the derived classes should be perfectly substitutable for their base classes. If class D is derived from A then D should be substitutable for A.

Look at the following C# code sample where the LSP is broken. Simply, an Orange cannot substitute an Apple, which results in printing the color of apple as Orange.

namespace SolidDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            Apple apple = new Orange();
            Console.WriteLine(apple.GetColor());
        }
    }
 
    public class Apple
    {
        public virtual string GetColor()
        {
            return "Red";
        }
    }
 
    public class Orange : Apple
    {
        public override string GetColor()
        {
            return "Orange";
        }
    }
}

Now let us re-factor and make it comply with LSP by having a generic base class for both Apple and Orange.

namespace SolidDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            Fruit fruit = new Orange();
            Console.WriteLine(fruit.GetColor());
            fruit = new Apple();
            Console.WriteLine(fruit.GetColor());
        }
    }
 
    public abstract class Fruit
    {
        public abstract string GetColor();
    }
 
    public class Apple : Fruit
    {
        public override string GetColor()
        {
            return "Red";
        }
    }
 
    public class Orange : Apple
    {
        public override string GetColor()
        {
            return "Orange";
        }
    }
}

Interface Segregation Principle (ISP)

ISP states that no clients should be forced to implement methods which it does not use and the contracts should be broken down to thin ones.

Say for example when a thick interface is defined declaring a wide responsibility of members then there will be opportunities where some clients may have to implement members, which they don’t even use. In the below mentioned example ISP is violated where ProcessCreditCard method is not required by InpersonOrder class but is forced to implement.

    public interface IOrder
    {
        void Purchase();
        void ProcessCreditCard();
    }
 
    public class OnlineOrder : IOrder
    {
        public void Purchase()
        {
            //Do purchase
        }
 
        public void ProcessCreditCard()
        {
            //process through credit card
        }
    }
 
    public class InpersionOrder : IOrder
    {
        public void Purchase()
        {
            //Do purchase
        }
 
        public void ProcessCreditCard()
        {
            //Not required for inperson purchase
            throw new NotImplementedException();
        }
    }

Now let us fix the violation by breaking down the IOrder interface.

public interface IOrder
    {
        void Purchase();
    }
 
    public interface IOnlineOrder
    {
        void ProcessCreditCard();
    }
 
    public class OnlineOrder : IOrder, IOnlineOrder
    {
        public void Purchase()
        {
            //Do purchase
        }
 
        public void ProcessCreditCard()
        {
            //process through credit card
        }
    }
 
    public class InpersionOrder : IOrder
    {
        public void Purchase()
        {
            //Do purchase
        }
    }

Dependency Inversion Principle (DIP)

DIP states that the higher level modules should be coupled with the lower level modules with complete abstraction.

You can read more about DIP and IoC in my earlier article where I have covered it in-depth. Please check here.

I hope this article took you through SOLID principles with appropriate C# code samples. Happy reading!



Related Articles

Comments

  • Great explanation

    Posted by MSA on 07/30/2017 04:04am

    Thanks for such simple explanation with examples.Very well done!

    Reply
  • LSP orange inheritance

    Posted by mb on 07/12/2017 05:39am

    Shouldn't Orange be inherited from Fruit, as well?

    Reply
  • Nice article

    Posted by Mohamed Ahmed on 05/06/2017 11:46pm

    This is so far the best explanation of SOLID principles I've ever read, so many who tried to explain them don't understand how important it is to have such clear and straightforward examples. Thanks

    Reply
  • Nice Article with little improvement in OCP

    Posted by Jaiprakash Barnwal on 04/18/2017 09:57pm

    Very nice article! Looks we can do some improvement in Open Close Principle code 1. change IEnumerable _cars; with IEnumerable _cars; 2. Remove CarController, we have GetMileage() method in ICar.

    Reply
  • Open Closed Principle

    Posted by saqib on 04/15/2017 04:01am

    Thanks for so detailed article about SOLID in C#.. I have on question...the code for OCP contains IEnumerable _cars; in class MileageCalculator..what is ..compiler shows error..apart from this i have added all references. but code is not working... please. guide me..

    Reply
  • Solid

    Posted by John on 12/29/2016 02:59am

    Well explained article, Thumbs up.

    Reply
  • SOLID

    Posted by Satish Rasal on 11/20/2016 08:34am

    Well explained with examples.

    • IT developer

      Posted by Subbaraja on 12/28/2016 10:58pm

      Very well explanined in simple manner

      Reply
    Reply
  • SOLID

    Posted by udaiveer on 04/21/2015 05:13am

    Nice article with good example.

    • SOLID Principles

      Posted by Chandrani on 11/02/2016 03:05am

      very clear explanation thank you

      Reply
    • solid patterns

      Posted by upen on 01/11/2016 02:09am

      Hi Arun, Very Excellent,understand now.Usually we do implement these without understand the solid. Thank You,

      Reply
    Reply
  • Mixing up the examples

    Posted by Ano on 09/12/2014 08:09am

    SRP - There is no need to add the interface for the gate utility into the SRP example as that is part of a different principle. Also it is more related to inheritance vs composition later on. Choose a set of classes up-front as an example and work your way through them applying each principle, improving the same classes step by step along the way and not "magically" apply a principle as part of another.

    Reply
  • Ravi K

    Posted by Ravi Kumar Kasim on 09/03/2014 06:56am

    OCP - example is mess up.How the CarController Cars properties collection reflects to MileageCalculator enumerable Cars property.There is no relation. Can you please explain the clearly this example.

    • OCP

      Posted by Austin on 08/07/2017 08:45pm

      I think there is no relation. Just assume that "IEnumberable cars" property has a list of actual cars whereas the one (which is List cars) created on CarController is solely for calculating mileage. That means you could have hundreds of audi cars with different name, which you will pass-in in MileageCalcular, but they have same mileage which you get that value from CarController. Well, that's what I think.

      Reply
    • OCP

      Posted by Austin on 08/07/2017 08:44pm

      I think there is no relation. Just assume that "IEnumberable cars" property has a list of actual cars whereas the one (which is List cars) created on CarController is solely for calculating mileage. That means you could have hundreds of audi cars with different name, which you will pass-in in MileageCalcular, but they have same mileage which you get that value from CarController. Well, that's what I think.

      Reply
    Reply
  • Loading, Please Wait ...

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • As all sorts of data becomes available for storage, analysis and retrieval - so called 'Big Data' - there are potentially huge benefits, but equally huge challenges...
  • The agile organization needs knowledge to act on, quickly and effectively. Though many organizations are clamouring for "Big Data", not nearly as many know what to do with it...
  • Cloud-based integration solutions can be confusing. Adding to the confusion are the multiple ways IT departments can deliver such integration...

Most Popular Programming Stories

More for Developers

RSS Feeds

Thanks for your registration, follow us on our social networks to keep up-to-date