Dependency Inversion Principle in C#

Dependency Inversion Principle in C# with Examples

In this article, I will 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, also known as DIP. You will understand the following pointers in detail at the end of this article.

  1. What is the Dependency Inversion Principle in C#?
  2. Example without using Dependency Inversion Principle in C# 
  3. Example using Dependency Inversion Principle in C# 
  4. Advantages and Disadvantages of using the Dependency Inversion Principle in C#
  5. How to use 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 you need to remember while developing real-time applications is always to keep the High-level and Low-level modules 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 unclear now, 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 the Dependency Inversion Principle with one Example using C# Language. First, we will see the example without following the Dependency Inversion Principle. Then, we will identify the problems of not following the Dependency Inversion Principle. 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 named Employee.cs and copy and paste the following code. 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 named EmployeeDataAccessLogic.cs and copy and paste the following code. 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, we have hard-coded the employee details here. 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 named DataAccessFactory.cs and copy and paste the following code. The following class contains one static method, 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, the 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 class will 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 named EmployeeBusinessLogic.cs and copy and paste the following code. The following class has one constructor 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 also have one method, i.e., GetEmployeeDetails, which calls the GetEmployeeDetails method on the EmployeeDataAccessLogic instance to get the employee details 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 the 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 the High-Level Module (class) and 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 the 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. In the later part of this article, I will modify the example to follow the Dependency Inversion Principle.

The second rule of the Dependency Inversion Principle states 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 Non-Concrete. So, Abstraction in Programming means we must create either an Interface or an Abstract Class, which is Non-Concrete, so we can not create an instance of it. In our example, the EmployeeBusinessLogic and EmployeeDataAccessLogic are concrete classes, meaning 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 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, you must explicitly declare the methods as abstract by using the abstract keyword. I am going with Interface as it makes the code more loosely coupled, and we can achieve multiple inheritances. 

IEmployeeDataAccessLogic.cs

Create a class file named IEmployeeDataAccessLogic.cs, then copy and paste the following code. As you can see, we created the interface with one abstract method, i.e., GetEmployeeDetails. You must declare those methods here if you have multiple employee-related methods.

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

We need to change the EmployeeBusinessLogic class, which will use the IEmployeeDataAccessLogic instead of the concrete EmployeeDataAccessLogic class, as shown below. You can see the EmployeeBusinessLogic class is not using the concrete EmployeeDataAccessLogic class. Instead, it uses the nonconcrete 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, 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

The EmployeeBusinessLogic and EmployeeDataAccessLogic classes are loosely coupled because EmployeeBusinessLogic does not depend on the concrete EmployeeDataAccessLogic class. Instead, it includes a reference to 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 make any changes to the EmployeeBusinessLogic class.

Here’s a simple example demonstrating the use of the Dependency Inversion Principle in C#. In the below example, the OrderProcessor high-level module depends on the ILogger abstraction rather than a concrete logger implementation. Dependency injection injects the appropriate logger implementation (such as FileLogger) into the OrderProcessor. This follows the Dependency Inversion Principle by inverting the dependencies and promoting loose coupling.

namespace SOLID_PRINCIPLES.DIP
{
    // Abstraction
    public interface ILogger
    {
        void Log(string message);
    }

    // Low-level module
    public class FileLogger : ILogger
    {
        public void Log(string message)
        {
            // Log message to a file
        }
    }

    // High-level module
    public class OrderProcessor
    {
        private readonly ILogger _logger;

        public OrderProcessor(ILogger logger)
        {
            _logger = logger;
        }

        public void ProcessOrder(Order order)
        {
            // Process order logic
            _logger.Log("Order processed: " + order.Id);
        }
    }

    public class Order
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }
}

By following these steps and designing your modules to depend on abstractions, you create a flexible and maintainable codebase that is adaptable to change and promotes good design practices.

In the next article, I will discuss Multiple Real-Time Examples of the Dependency Inversion Principle (DIP) in C#. In this article, I 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, questions, or comments about this Dependency Inversion Principle article. With this, we have completed SOLID Design Principles using C# Language. Please give your valuable feedback in the comment section. 

36 thoughts on “Dependency Inversion Principle in C#”

  1. 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. 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. 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 *