×

iFour Logo

A Comprehensive Guide on C# SOLID Principles

Kapil Panchal - September 23, 2021

Listening is fun too.

Straighten your back and cherish with coffee - PLAY !

  • play
  • pause
  • pause
A Comprehensive Guide on C# SOLID Principles

Table of Content

In C#, SOLID design principles are basic design principles. SOLID Principles enable us to overcome most of the design problems. In the 1990s, Robert Martin compiled these principles. These principles provided us with many ways to overcome tight coupled code and least encapsulation.

Usually, Developers start the application building process with good and clean designs according to their experience. But, as time passes, applications might develop bugs. The application has to be changed for every new feature or change request. After a long time, we might need to put more effort, even for small tasks and it might require a lot of time and energy. But as they are part of software development, we cannot refuse them as well.

Here are few design issues which are causing damage in any software in most cases:

  1. When we put more stress on specific classes by assigning them more responsibilities to them which are not related to that class.
  2. When we force the class to depend on each other or when they are tightly coupled.
  3. When we use duplicate code in the application

To overcome these issues, we can implement the following tactics:

  1. We must choose the right architecture for the application
  2. We must follow the design principles made by experts.
  3. We must choose the correct design patterns to build the software according to its specifications and needs.

Here, SOLID stands for as shown below:

S = Single Responsibility Principle

O = Open Closed Principle

L = Liskov Substitution Principle

I = Interface Segregation Principle

D = Dependency Inversion Principle

Single Responsibility Principle (SRP):


As per the concepts of the Single Responsibility Principle, every module of the software must have only a single reason to change. Everything in that particular class

This means, that every single class or class-like structure in our code must have only one job to perform. All the things in that class must be related to one single purpose. Our class must not be multifunctional at all.

The Single Responsibility Principle represents a way of recognizing classes at the App’s design phase making you think of all the ways a class can change. When you see an excellent clarity of operation requests, a fine separation of responsibilities occurs.

Let us comprehend this with an example.

 
                  public class UserService
                  {
                    EmailService _emailService;
                    DbContext _dbContext;
                    public UserService(EmailService aEmailService, DbContext aDbContext)
                    {
                      _emailService = aEmailService;
                      _dbContext = aDbContext;
                    }
                    public void Register(string email, string password)
                    {
                      if (!_emailService.ValidateEmail(email))
                      throw new ValidationException("Email is not an email");
                      var user = new User(email, password);
                      _dbContext.Save(user);
                      emailService.SendEmail(new MailMessage("myname@mydomain.com", email) {Subject="Hi. How are you!"});
                    }
                  }
                  public class EmailService
                  {
                    SmtpClient _smtpClient;
                    public EmailService(SmtpClient aSmtpClient)
                    {
                      _smtpClient = aSmtpClient;
                    }
                    public bool virtual ValidateEmail(string email)
                    {
                      return email.Contains("@");
                    }
                    public bool SendEmail(MailMessage message)
                    {
                      _smtpClient.Send(message);
                    }
                  }

Open-Closed Principle (OCP):


The Open/closed principle says "A software module/class is opened for the extension and closed for editing".

Here, "Open for extension" means that we should design our module/class so that the new feature can only be added when new requirements are generated. Here, "Open for extension" means that we should design our module/class so that the new functionality can only be added when new requirements are created. We should never tamper with it until we find some bugs. As mentioned, a class should be open for extensions, we can use inheritance for this. OK, let's take one example.

 
public class Rectangle: Shape
                  {
                    public double Height {get;set;}
                    public double Width {get;set;}
                    public override double Area()
                    {
                      return Height * Width;
                    }
                  }
                  public class Circle: Shape
                  {
                    public double Radius {get;set;}
                    public override double Area()
                    {
                      return Radius * Radius * Math.PI;
                    }
                  }
                  
                  public class AreaCalculator
                  {
                    public double TotalArea(Shape[] arrShapes)
                    {
                      double area=0;
                      foreach(var objShape in arrShapes)
                      {
                        area += objShape.Area();
                      }
                      return area;
                    }  
                  }

Now our code tracks SRP and OCP at once. When you introduce a new form deriving from the abstract class "Shape", you do not need to change the class "AreaCalculator".

Liskov Substitution Principle (LSP):


The Liskov Substitution Principle (LSP) states that "you should be able to use any derived class instead of a parent class and have it behave in the same manner without modification". It guarantees that a derived class does not affect the behavior of the parent class, in other words, that a derived class must be substitutable for its basic class.

This principle is just an extension of the Open-Closed Principle and it means that we need to make sure that the new derived classes extend the basic classes without changing their behavior. I will explain it by employing a concrete example that violates the LSP.

A dad is a doctor and his son wants to be a cricket player. So here the son cannot replace his father even if the two belong to the same family hierarchy.

 
   public class SqlFileManager
{
  public string GetTextFromFiles(List aLstReadableFiles)
  {
    StringBuilder objStrBuilder = new StringBuilder();
    foreach(var objFile in aLstReadableFiles)
    {
      objStrBuilder.Append(objFile.LoadText());
    }
    return objStrBuilder.ToString();
  }
  public void SaveTextIntoFiles(List aLstWritableFiles)
  {
    foreach(var objFile in aLstWritableFiles)
    {
      objFile.SaveText();
    }
  }
}

              

Looking for Trusted C# Development Company ?
For Your Business?

Interface Segregation Principle (ISP):


The principle of segregation of interfaces stipulates that customers should not be forced to implement interfaces they do not use. Rather than a fat interface, many small interfaces are preferred depending on method groups, each serving as a sub-module.

It may be defined differently. An interface should be closer to the code which uses it than to the code which implements it. So the methods of the interface are defined by the methods that the client code needs instead of the methods that the class implements. As a result, clients should not be forced to depend on interfaces they do not use.

As with classes, each interface needs to have a specific purpose/responsibility (see SRP). You should not have to implement an interface when your object does not share this goal. The larger the interface, the more likely it is that it includes methods that not every implementer can do. That's the essence of the Interface Segregation Principle. Let's start with an example.

 
                public class TeamLead: IProgrammer, ILead
                {
                  public void AssignTask()
                  {
                    //Code to assign a Task
                  }
                  public void CreateSubTask()
                  {
                    //Code to create a sub-task from a task.
                  }
                  public void WorkOnTask()
                  {
                    //code to implement to work on the Task.
                  }
                }

              

Dependency Inversion Principle (DIP):


The Dependency Inversion Principle says that all the high-level modules and classes must not depend on low-level modules and classes. All must depend upon abstractions. Also, abstractions must not depend upon details of it, instead, they should depend upon abstractions.

 
  public class DataExporter
{
  public void ExportDataFromFile()
  {
    ExceptionLogger _exceptionLogger;
    try 
    {
      //code to export data from files to database.
    }
    catch(IOException ex)
    {
      _exceptionLogger = new ExceptionLogger(new DbLogger());
      _exceptionLogger.LogException(ex);
    }
    catch(SqlException ex)
    {
      _exceptionLogger = new ExceptionLogger(new EventLogger());
      _exceptionLogger.LogException(ex);
    }
    catch(Exception ex)
    {
      _exceptionLogger = new ExceptionLogger(new FileLogger());
      _exceptionLogger.LogException(ex);
    }
  }
}
                

Conclusion


In this article, we discussed the C# SOLID principles for better coding standards and how to make it as fruitful as possible. We also saw a few examples to deeply understand these concepts.

 
A Comprehensive Guide on C# SOLID Principles Table of Content 1. Single Responsibility Principle (SRP) 2. Open-Closed Principle (OCP) 3. Liskov Substitution Principle (LSP) 4. Interface Segregation Principle (ISP) 5. Dependency Inversion Principle (DIP) 6. Conclusion In C#, SOLID design principles are basic design principles. SOLID Principles enable us to overcome most of the design problems. In the 1990s, Robert Martin compiled these principles. These principles provided us with many ways to overcome tight coupled code and least encapsulation. Usually, Developers start the application building process with good and clean designs according to their experience. But, as time passes, applications might develop bugs. The application has to be changed for every new feature or change request. After a long time, we might need to put more effort, even for small tasks and it might require a lot of time and energy. But as they are part of software development, we cannot refuse them as well. Here are few design issues which are causing damage in any software in most cases: When we put more stress on specific classes by assigning them more responsibilities to them which are not related to that class. When we force the class to depend on each other or when they are tightly coupled. When we use duplicate code in the application To overcome these issues, we can implement the following tactics: We must choose the right architecture for the application We must follow the design principles made by experts. We must choose the correct design patterns to build the software according to its specifications and needs. Here, SOLID stands for as shown below: S = Single Responsibility Principle O = Open Closed Principle L = Liskov Substitution Principle I = Interface Segregation Principle D = Dependency Inversion Principle Single Responsibility Principle (SRP): As per the concepts of the Single Responsibility Principle, every module of the software must have only a single reason to change. Everything in that particular class This means, that every single class or class-like structure in our code must have only one job to perform. All the things in that class must be related to one single purpose. Our class must not be multifunctional at all. The Single Responsibility Principle represents a way of recognizing classes at the App’s design phase making you think of all the ways a class can change. When you see an excellent clarity of operation requests, a fine separation of responsibilities occurs. Let us comprehend this with an example.   public class UserService { EmailService _emailService; DbContext _dbContext; public UserService(EmailService aEmailService, DbContext aDbContext) { _emailService = aEmailService; _dbContext = aDbContext; } public void Register(string email, string password) { if (!_emailService.ValidateEmail(email)) throw new ValidationException("Email is not an email"); var user = new User(email, password); _dbContext.Save(user); emailService.SendEmail(new MailMessage("myname@mydomain.com", email) {Subject="Hi. How are you!"}); } } public class EmailService { SmtpClient _smtpClient; public EmailService(SmtpClient aSmtpClient) { _smtpClient = aSmtpClient; } public bool virtual ValidateEmail(string email) { return email.Contains("@"); } public bool SendEmail(MailMessage message) { _smtpClient.Send(message); } } Read More: Detailed Overview Of Design Patterns In C-sharp Open-Closed Principle (OCP): The Open/closed principle says "A software module/class is opened for the extension and closed for editing". Here, "Open for extension" means that we should design our module/class so that the new feature can only be added when new requirements are generated. Here, "Open for extension" means that we should design our module/class so that the new functionality can only be added when new requirements are created. We should never tamper with it until we find some bugs. As mentioned, a class should be open for extensions, we can use inheritance for this. OK, let's take one example.   public class Rectangle: Shape { public double Height {get;set;} public double Width {get;set;} public override double Area() { return Height * Width; } } public class Circle: Shape { public double Radius {get;set;} public override double Area() { return Radius * Radius * Math.PI; } } public class AreaCalculator { public double TotalArea(Shape[] arrShapes) { double area=0; foreach(var objShape in arrShapes) { area += objShape.Area(); } return area; } } Now our code tracks SRP and OCP at once. When you introduce a new form deriving from the abstract class "Shape", you do not need to change the class "AreaCalculator". Liskov Substitution Principle (LSP): The Liskov Substitution Principle (LSP) states that "you should be able to use any derived class instead of a parent class and have it behave in the same manner without modification". It guarantees that a derived class does not affect the behavior of the parent class, in other words, that a derived class must be substitutable for its basic class. This principle is just an extension of the Open-Closed Principle and it means that we need to make sure that the new derived classes extend the basic classes without changing their behavior. I will explain it by employing a concrete example that violates the LSP. A dad is a doctor and his son wants to be a cricket player. So here the son cannot replace his father even if the two belong to the same family hierarchy.   public class SqlFileManager { public string GetTextFromFiles(List aLstReadableFiles) { StringBuilder objStrBuilder = new StringBuilder(); foreach(var objFile in aLstReadableFiles) { objStrBuilder.Append(objFile.LoadText()); } return objStrBuilder.ToString(); } public void SaveTextIntoFiles(List aLstWritableFiles) { foreach(var objFile in aLstWritableFiles) { objFile.SaveText(); } } } Looking for Trusted C# Development Company ? For Your Business? CONNECT US Interface Segregation Principle (ISP): The principle of segregation of interfaces stipulates that customers should not be forced to implement interfaces they do not use. Rather than a fat interface, many small interfaces are preferred depending on method groups, each serving as a sub-module. It may be defined differently. An interface should be closer to the code which uses it than to the code which implements it. So the methods of the interface are defined by the methods that the client code needs instead of the methods that the class implements. As a result, clients should not be forced to depend on interfaces they do not use. As with classes, each interface needs to have a specific purpose/responsibility (see SRP). You should not have to implement an interface when your object does not share this goal. The larger the interface, the more likely it is that it includes methods that not every implementer can do. That's the essence of the Interface Segregation Principle. Let's start with an example.   public class TeamLead: IProgrammer, ILead { public void AssignTask() { //Code to assign a Task } public void CreateSubTask() { //Code to create a sub-task from a task. } public void WorkOnTask() { //code to implement to work on the Task. } } Dependency Inversion Principle (DIP): The Dependency Inversion Principle says that all the high-level modules and classes must not depend on low-level modules and classes. All must depend upon abstractions. Also, abstractions must not depend upon details of it, instead, they should depend upon abstractions.   public class DataExporter { public void ExportDataFromFile() { ExceptionLogger _exceptionLogger; try { //code to export data from files to database. } catch(IOException ex) { _exceptionLogger = new ExceptionLogger(new DbLogger()); _exceptionLogger.LogException(ex); } catch(SqlException ex) { _exceptionLogger = new ExceptionLogger(new EventLogger()); _exceptionLogger.LogException(ex); } catch(Exception ex) { _exceptionLogger = new ExceptionLogger(new FileLogger()); _exceptionLogger.LogException(ex); } } } Conclusion In this article, we discussed the C# SOLID principles for better coding standards and how to make it as fruitful as possible. We also saw a few examples to deeply understand these concepts.  
Kapil Panchal

Kapil Panchal

A passionate Technical writer and an SEO freak working as a Content Development Manager at iFour Technolab, USA. With extensive experience in IT, Services, and Product sectors, I relish writing about technology and love sharing exceptional insights on various platforms. I believe in constant learning and am passionate about being better every day.

Build Your Agile Team

Enter your e-mail address Please enter valid e-mail

Categories

Ensure your sustainable growth with our team

Talk to our experts
Sustainable
Sustainable
 
Blog Our insights
10 Executive Dashboard Examples for Consultants and CEOs
10 Executive Dashboard Examples for Consultants and CEOs

There is a principle behind every business. “If you don’t keep track of essentials, you won’t get clear direction, eventually causing your company to stumble.” To manage this scenario,...

How Spatial Data Analysis Improves Healthcare
How Spatial Data Analysis Improves Healthcare

Do you know when geospatial analysis took traction in healthcare? It was when John Snow, a London-based physician, used it to analyze the spread of cholera, which ultimately proved...

4 Types of Power BI Dashboards: Analytical, Strategic, Operational, and Tactical
4 Types of Power BI Dashboards: Analytical, Strategic, Operational, and Tactical

One interesting aspect you truly love about Power BI, as a CTO, is how it lets you step back and see the bigger picture of your business. Isn’t it? Without getting bogged down in minute...