SOLID Principles in C#

The SOLID principles is a standard software development practice that should be followed in all software applications which is using Object Oriented Programming. They set the norm of how to write a program with clean architecture. SOLID principles in C# helps to write code which is easy to test, easy to maintain and easy to extend.

In this article, we will see about essential information on SOLID principles and we will see the major advantages of using SOLID Principles in C# based application.

Develop a flexible , testable and maintainable software application with SOLID principles in C#. It consists of 5 principles.

Solid principles in C#

5 SOLID Principles in C#

In this article, we’ll bring a profound plunge into every one of these 5 principles and clarify how they work with C# code. Don’t worry, if the concept is clear, then you can apply it with other programming languages that suits well.

S (Single Responsibility)

The very first principle says that a class should be created for a single specific purpose. It means a class should not be responsible for multiple tasks.

The very basic example to understand this principle is User Registration process. In UserRegisteration Service class, if we write code which send welcome message or credential details, this violates Single Responsibility principle.

public class Userservice
    {
        public void UserRegisteration()
        {
            //Do the Registration Process
        }
        public bool Login(string userName, string password)
        {
            try
            {
                return true;
            }

            catch
            {
                return false;
            }
        }

        public void SendEmail()
        {
            //Send Mail to newly registered users
        }
    }

Instead, create a different class for Email Service and then after successful registration, we may call a function written in Email Service to send an email to users.

 public class Userservice
    {
        public void UserRegisteration()
        {
            //Do the Registration Process
        }
        public bool Login(string userName, string password)
        {
            try
            {
                return true;
            }

            catch
            {
                return false;
            }
        }
       
    }

    public class MailerService
    {
        public void SendEmail()
        {
            //Send Mail to newly registered users
        }
    }

O (Open Close Principle)

This principle says that a class or function should be open for extension but should be closed for modification. This is one of the very important SOLID Principles in C#.

Consider Below example –

In the below code snippet, suppose a new docType is required then this class needs a modification and we need to add one more condition to achieve this. This violates the Open Close Principle.

    public class ExportDocument
    {
       public void Export(string docType)
        {
            if(docType == "pdf")
            {
                //Export Document in PDF Format
            }

            else if (docType == "word")
            {
                //Export Document in Word Format
            }

            else if (docType == "excel")
            {
                //Export Document in Excel Format
            }
        }
    }

To overcome this issue, we can create an interface.

 public interface IExportDocument
    {
        public void Export();
    }

Next, create classes as per document type to export and inherit it from the interface as created above.

 public class ExportDocumentInPDFFormat : IExportDocument
    {
        public void Export()
        {
            //Export Document in PDF format
        }
    }

    public class ExportDocumentInWordFormat : IExportDocument
    {
        public void Export()
        {
            //Export Document in Word format
        }
    }

    public class ExportDocumentInExcelFormat : IExportDocument
    {
        public void Export()
        {
            //Export Document in Excel format
        }
    }

In case a new document type is required to export then simply create a class as shown below and inherit it from IExportDocument. In this way, none of the existing class is required to modify and we follow the Open Close Principle.

 public class ExportDocumentInCSVFormat : IExportDocument
    {
        public void Export()
        {
            //Export Document in CSV format
        }
    }

We can use abstract class instead of interface to achieve this principle.

L (Liskov substitution Principle)

If S1 is a subtype of T1 then object of S1 may replace the object of T1. It means derived types can be substituted for the base types.

This principle can be achieved with the help of interface or abstract class. Here, I have used abstract class.

 public abstract class Car
    {
        public abstract string GetCarModel();
    }
public class Audi : Car
    {
        public override string GetCarModel()
        {
            return "A4";
        }
    }

    public class BMW : Car
    {
        public override string GetCarModel()
        {
            return "X5";
        }
    }

Now, with a car object we will call methods of Audi and BMW classes as Car is the parent class.

static void Main(string[] args)
        {
            Car car = new Audi();
            Console.WriteLine(car.GetCarModel());
            car = new BMW();
            Console.WriteLine(car.GetCarModel());
        }

I (Interface Segregation Principle)

The Interface Segregation Principle says that there should be multiple Interfaces instead of single interface. This will not force the client to implement interfaces they do not require.

Consider below example –

There is one interface which contains 2 methods, the first method is to export and format the document and second method is to send that document over email.

 interface ExportDocumentFormat
    {
        void DocumentFormatter();
        void SendDocumentMail();
    }

Suppose, we have one class which do document export/ formatting and send email. This is absolutely fine.

public class ExportDcoumentAndSendMail : ExportDocumentFormat
    {
        public void DocumentFormatter()
        {
           //Export and Format Document
        }

        public void SendDocumentMail()
        {
            //Send Email
        }
    }

Next, we have one more class which does only one task i.e. document formatting. This class doesn’t require Email related functionality. But, we are forced to implement this as this class inherits an interface which contains both the methods. Here we are violating the principle “Interface Segregation Principle”

public class ExportDcoumentOnly : ExportDocumentFormat
    {
        public void DocumentFormatter()
        {
            //Export and Format Document
        }

        public void SendDocumentMail()
        {
            //Send Email
        }
    }

To follow this principle, we will segregate the interface into 2 parts. You can do this based on your requirement.

So, Let’s refactor the above code to follow the Interface Segregation Principle.

One interface segregated into 2

interface ExportDocumentFormat
    {
        void DocumentFormatter();
    }

    interface SendDocumentOverMail
    {
        void SendDocumentMail();
    }

The class which needs both the methods, we will implement it with both interfaces (Multiple inheritance) and the class which needs only method then we can inherit this accordingly.

public class ExportDcoumentAndSendMail : ExportDocumentFormat, SendDocumentOverMail
    {
        public void DocumentFormatter()
        {
           //Export and Format Document
        }

        public void SendDocumentMail()
        {
            //Send Email
        }
    }


    public class ExportDcoumentOnly : ExportDocumentFormat
    {
        public void DocumentFormatter()
        {
            //Export and Format Document
        }
 
    }

D (Dependency Inversion Principle)

This SOLID design principle says that a high level module shouldn’t depend upon low level module. It basically avoids tight coupling and help us to develop an application which code is testable, maintainable.

Let’s understand the benefit of the Dependency Inversion Principle with below code snippet.

I have create an interface and called it IDocumentPrinting

    public interface IDocumentPrinting
    {
        void PDFPrinting();
        void MSWordPrinting();
    }

Next, created one class which inherits IDocumentPrinting interface and did the implementation in ProjectModule1 class.

 public class ProjectModule1 : IDocumentPrinting
    {
        public void PDFPrinting()
        {
            Console.WriteLine("PDF Printing for Module1");
        }
        public void MSWordPrinting()
        {
            Console.WriteLine("MS-Word Printing for Module1");
        }
       
    }

Create a controller class which calls the methods of IDocumentPrinting interface.

public class FileFormatController
    {
        IDocumentPrinting _documentPrinting;

        public FileFormatController(IDocumentPrinting documentPrinting)
        {
            this._documentPrinting = documentPrinting;
        }

        public void PDFFile()
        {
            _documentPrinting.PDFPrinting();
        }

        public void WordFile()
        {
            _documentPrinting.MSWordPrinting();
        }
    }

Finally, call PDFPrinting() and MSWordPrinting() method as shown in below code snippet.

 static void Main(string[] args)
        {
            IDocumentPrinting documentPrinting = new ProjectModule1();
            FileFormatController fileController = new FileFormatController(documentPrinting);
            documentPrinting.PDFPrinting();
            documentPrinting.MSWordPrinting();
            Console.Read();
        }

We are yet to implement the benefit of Dependency Injection Principle.

Let’s assume that due to some scenario we have to create one another module which will have similar methods but there will be some differences. Now, the real game starts.

We will just create a new class ProjectModule2 and will inherit with same interface i.e. IDocumentPrinting

 public class ProjectModule2 : IDocumentPrinting
    {
        public void PDFPrinting()
        {
            Console.WriteLine("PDF Printing for Module2");
        }

        public void MSWordPrinting()
        {
            Console.WriteLine("MS-Word Printing for Module2");
        }
    }

In the main method, just use below code snippet. You may notice that instead of ProjectModule1 I have used ProjectModule2 and it calls the method of the newly created class.

 static void Main(string[] args)
        {
            IDocumentPrinting documentPrinting = new ProjectModule2();
            FileFormatController fileController = new FileFormatController(documentPrinting);
            documentPrinting.PDFPrinting();
            documentPrinting.MSWordPrinting();
            Console.Read();
        }

In this way, we have refactored our source code to maintain Dependency Inversion Principle. DI Principle is mainly to focus on code maintainability which helps the developer for future requirement and related changes.

Summary:

SOLID Principles helps to write better code in C# programming language. All the approach given in SOLID principles have their significant advantages. The main purpose of SOLID principle is to minimize the impact of changes in existing software application.

SOLID Principles consists of 5 principles –

  • S – Single Responsibility- It tells that a class should not be assigned many jobs to do.
  • O- Open Close Principle – This principle says a class or module should be open for extension but should be close for modification.
  • L – Liskov Substitution Principle – This principle implies that derived types can be substituted for the base types.
  • I – Interface Segregation Principle – This part of SOLID principle says don’t overload the interface with too many methods. Try to use multiple interface within your application instead of one fat interface.
  • D – Dependency Inversion Principle (DIP) – It basically avoid tight coupling and help us to develop an application which code is testable, maintainable.
Please follow and like us:

Leave a Comment