Dependency Inversion Principle in C#

Dependency Inversion Principle in C# with Examples

In this article, I am going to discuss the Dependency Inversion Principle in C# with Examples. Please read our previous article before proceeding to this article where we discussed the Interface Segregation Principle in C# with Examples. The Letter D in SOLID stands for the Dependency Inversion Principle which is also known as DIP. At the end of this article, you will understand the following pointers in detail.

  1. What is Dependency Inversion Principle in C#?
  2. Example without using Dependency Inversion Principle in C# 
  3. Example using Dependency Inversion Principle in C# 
  4. Advantages of using the Dependency Inversion Principle in C#
What is the Dependency Inversion Principle in C#?

The Dependency Inversion Principle (DIP) states that High-Level Modules/Classes should not depend on Low-Level Modules/Classes. Both should depend upon Abstractions. Secondly, Abstractions should not depend upon Details. Details should depend upon Abstractions.

The most important point that you need to remember while developing real-time applications is always to try to keep the High-level module and Low-level module as loosely coupled as possible.

When a class knows about the design and implementation of another class, it raises the risk that if we do any changes to one class will break the other class. So we must keep these high-level and low-level modules/classes loosely coupled as much as possible. To do that, we need to make both of them dependent on abstractions instead of knowing each other. If this is not clear at the moment, then don’t worry, at the end of this article, you will understand this concept very well with examples.

Example to Understand Dependency Inversion Principle in C# 

Let us understand Dependency Inversion Principle with one Example using C# Language. First, we will see the example without following the Dependency Inversion Principle and then we will identify the problems of not following the Dependency Inversion Principle, and then we will rewrite the same example using the Dependency Inversion Principle so that you will understand this concept easily. First, create a Console Application and then add the following class files.

Employee.cs

Create a class file with the name Employee.cs and then copy and paste the following code into it. The following is a simple class having 4 properties. The following class is going to hold the employee data.

namespace SOLID_PRINCIPLES.DIP
{
    public class Employee
    {
        public int ID { get; set; }
        public string Name { get; set; }
        public string Department { get; set; }
        public int Salary { get; set; }
    }
}
EmployeeDataAccessLogic.cs

Create a class file with the name EmployeeDataAccessLogic.cs and then copy and paste the following code into it. The following class contains one method which takes the employee id and returns that Employee information. In a real-time application, you need to write the logic to get the employee details from the database but for simplicity here we have hard-coded the employee details. Also, in a real-time application, you might have more methods like getting all employee information, creating and updating employees, deleting employees, etc. Here, we have created only one method. So, this is the layer that will contain the logic to interact with the database and hence we said this class as EmployeeDataAccessLogic.

namespace SOLID_PRINCIPLES.DIP
{
    public class EmployeeDataAccessLogic
    {
        public Employee GetEmployeeDetails(int id)
        {
            //In real time get the employee details from database
            //but here we have hard coded the employee details
            Employee emp = new Employee()
            {
                ID = id,
                Name = "Pranaya",
                Department = "IT",
                Salary = 10000
            };
            return emp;
        }
    }
}
DataAccessFactory.cs

Create a class file with the name DataAccessFactory.cs and then copy and paste the following code into it. The following class contains one static method which is returning an instance of the EmployeeDataAccessLogic class. If you want to consume any method of the EmployeeDataAccessLogic class, then you need to create an instance of that class. In our example, the following class, GetEmployeeDataAccessObj() static method is going to return an instance of the EmployeeDataAccessLogic class and using that instance we can access the GetEmployeeDetails(int id) method. So, this is the class that is going to return an instance of the EmployeeDataAccessLogic class using which we can do the database operations.

namespace SOLID_PRINCIPLES.DIP
{
    public class DataAccessFactory
    {
        public static EmployeeDataAccessLogic GetEmployeeDataAccessObj()
        {
            return new EmployeeDataAccessLogic();
        }
    }
}
EmployeeBusinessLogic.cs

Create a class file with the name EmployeeBusinessLogic.cs and then copy and paste the following code into it. The following class has one constructor that is used to create an instance of the EmployeeDataAccessLogic class. Here, within the constructor we call the static GetEmployeeDataAccessObj() method on the DataAccessFactory class which will return an instance of EmployeeDataAccessLogic and we initialize the _EmployeeDataAccessLogic property with the return instance. We have also one method i.e. GetEmployeeDetails which is used to call the GetEmployeeDetails method on the EmployeeDataAccessLogic instance to get the employee detail by employee id.

namespace SOLID_PRINCIPLES.DIP
{
    public class EmployeeBusinessLogic
    {
        EmployeeDataAccessLogic _EmployeeDataAccessLogic;
        public EmployeeBusinessLogic()
        {
            _EmployeeDataAccessLogic = DataAccessFactory.GetEmployeeDataAccessObj();
        }
        public Employee GetEmployeeDetails(int id)
        {
            return _EmployeeDataAccessLogic.GetEmployeeDetails(id);
        }
    }
}
Comparing the above Example with Dependency Inversion Principle in C#

As per the Dependency Inversion Principle definition“a High-Level module should not depend on Low-Level modules. Both should depend on the abstraction”.

So, first, we need to figure out which one is the High-Level Module (class) and which one is the Low-Level Module (class) in our example. A High-Level Module is a module that always depends on other modules. So, in our example, the EmployeeBusinessLogic class depends on the EmployeeDataAccessLogic class, so here the EmployeeBusinessLogic class is the high-level module and EmployeeDataAccessLogic class is the low-level module.

So, as per the first rule of the Dependency Inversion Principle in C#, the EmployeeBusinessLogic class/module should not depend on the concrete EmployeeDataAccessLogic class/module, instead, both classes should depend on abstraction. But, in our example, the way we have implemented the code, the EmployeeBusinessLogic depending on the EmployeeDataAccessLogic class, means the first rule we are not following. Later part of this article, I will modify the example to follow Dependency Inversion Principle.

The second rule of the Dependency Inversion Principle state that “Abstractions should not depend on details. Details should depend on Abstractions”. Before understanding this let us first understand what is an abstraction.

What is Abstraction in C#?

In simple words, we can say that Abstraction means something which is Non-Concrete. So, Abstraction in Programming means we need to create either an Interface or an Abstract Class which is Non-Concrete so that we can not create an instance of it. In our example, the EmployeeBusinessLogic and EmployeeDataAccessLogic are concrete classes which means we can create objects of them. That means we are also not following the second rule of the Dependency Inversion Principle.

As per the Dependency Inversion Principle in C#, the EmployeeBusinessLogic (High-Level Module) should not depend on the concrete EmployeeDataAccessLogic (Low-Level Module) class. Both classes should depend on Abstractions, meaning both classes should depend on either an Interface or an Abstract Class.

What should be in Interface (or in Abstract Class)?

As you can see in the above example, EmployeeBusinessLogic uses the GetEmployeeDetails() method of the EmployeeDataAccessLogic class. In real-time, there will be many employee-related methods in the EmployeeDataAccessLogic class. So, we need to declare the GetEmployeeDetails(int id) method or any employee-related methods within the interface or abstract class. By default, interface methods are going to be abstract. But if you create an abstract class, then you need to declare the methods as abstract explicitly by using the abstract keyword. I am going with Interface as it makes the code more loosely coupled as well as we can also achieve multiple inheritances. 

IEmployeeDataAccessLogic.cs

Create a class file with the name IEmployeeDataAccessLogic.cs and then copy and paste the following code into it. As you can see, here we created the interface with one abstract method i.e. GetEmployeeDetails. If you have multiple employee-related methods, then you need to use declare those methods here.

namespace SOLID_PRINCIPLES.DIP
{
    public interface IEmployeeDataAccessLogic
    {
        Employee GetEmployeeDetails(int id);
        //Any Other Employee Related Method Declarations
    }
}

Next, we need to implement the IEmployeeDataAccessLogic in EmployeeDataAccessLogic class. So, modify the EmployeeDataAccessLogic class as shown below. Here, you can see, the EmployeeDataAccessLogic class implementing the IEmployeeDataAccessLogic class and providing implementations for the GetEmployeeDetails method.

namespace SOLID_PRINCIPLES.DIP
{
    public class EmployeeDataAccessLogic : IEmployeeDataAccessLogic
    {
        public Employee GetEmployeeDetails(int id)
        {
            //In real time get the employee details from database
            //but here we have hard coded the employee details
            Employee emp = new Employee()
            {
                ID = id,
                Name = "Pranaya",
                Department = "IT",
                Salary = 10000
            };
            return emp;
        }
    }
}

Next, we need to change the DataAccessFactory class. Here, we need to change the return type of the GetEmployeeDataAccessObj to IEmployeeDataAccessLogic instead of EmployeeDataAccessLogic. Internally, the method creates an instance of the EmployeeDataAccessLogic class but we return that instance to the user using the Parent Interface i.e. IEmployeeDataAccessLogic. This is possible because a Parent Class Reference Variable can hold the child class object reference. And here, IEmployeeDataAccessLogic is the Parent class and EmployeeDataAccessLogic is the Child class of the IEmployeeDataAccessLogic Parent class.

namespace SOLID_PRINCIPLES.DIP
{
    public class DataAccessFactory
    {
        public static IEmployeeDataAccessLogic GetEmployeeDataAccessObj()
        {
            return new EmployeeDataAccessLogic();
        }
    }
}

Now, we need to change the EmployeeBusinessLogic class which will use the IEmployeeDataAccessLogic instead of the concrete EmployeeDataAccessLogic class as shown below. Now, you can see, the EmployeeBusinessLogic class is not using the concrete EmployeeDataAccessLogic class instead it is using the not concrete IEmployeeDataAccessLogic class.

namespace SOLID_PRINCIPLES.DIP
{
    public class EmployeeBusinessLogic
    {
        IEmployeeDataAccessLogic _IEmployeeDataAccessLogic;
        public EmployeeBusinessLogic()
        {
            _IEmployeeDataAccessLogic = DataAccessFactory.GetEmployeeDataAccessObj();
        }
        public Employee GetEmployeeDetails(int id)
        {
            return _IEmployeeDataAccessLogic.GetEmployeeDetails(id);
        }
    }
}

That’s it. We have implemented the Dependency Inversion Principle in our example using C# language where the High-Level module (EmployeeBusinessLogic) and Low-Level module (EmployeeDataAccessLogic) depend on abstraction (IEmployeeDataAccessLogic). Also, abstraction (IEmployeeDataAccessLogic) does not depend on details (EmployeeDataAccessLogic) but details depend on abstraction.

Now, you can test whether the application code is working as expected or not by modifying the Main method of the Program class as follows. Here, we are simply creating an instance of the EmployeeBusinessLogic class and calling the GetEmployeeDetails method and then printing the employee details on the Console window.

using System;
namespace SOLID_PRINCIPLES.DIP
{
    public class Program
    {
        static void Main(string[] args)
        {
            EmployeeBusinessLogic employeeBusinessLogic = new EmployeeBusinessLogic();
            Employee emp = employeeBusinessLogic.GetEmployeeDetails(1001);
            Console.WriteLine($"ID: {emp.ID}, Name: {emp.Name}, Department: {emp.Department}, Salary: {emp.Salary}");
            Console.ReadKey();
        }
    }
}

When you run the above code, you will get the following output.

Dependency Inversion Principle in C# with Examples

Advantages of using the Dependency Inversion Principle in C#:

Now, the EmployeeBusinessLogic and EmployeeDataAccessLogic classes are loosely coupled classes because EmployeeBusinessLogic does not depend on the concrete EmployeeDataAccessLogic class, instead, it includes a reference of the IEmployeeDataAccessLogic interface. So now, we can easily use another class that implements IEmployeeDataAccessLogic with a different implementation and for that, we don’t need to do any changes to the EmployeeBusinessLogic class.

In this article, I try to explain the Dependency Inversion Principle in C# with Examples. I hope this article will help you with your needs. I would like to have your feedback. Please post your feedback, question, or comments about this Dependency Inversion Principle article. With this, we have completed SOLID Design Principles using C# Langauge. Please give your valuable feedback in the comment section. Next, you should learn Design Patterns using C#.

34 thoughts on “Dependency Inversion Principle in C#”

  1. blank

    I am very happy for finding this great site. I couldn’t skip writing a comment to thank you, keep the great work up!

  2. blank

    I have referred many different sources to understand SOLID principals along with this yours but I found this discussion as the best one. Simple to understand ,very clear with nice examples. Thank you so much.
    Could you please share a link for design patterns as well , singleton, factory method?
    Thank you again.

  3. blank

    Does returning the concrete Employee in IEmployeesDataAccess create the same problem as when we return the EmployeeDataAccess in DataAccessFactory?

Leave a Reply

Your email address will not be published. Required fields are marked *