Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Dependency Inversion Principle, IoC Container & Dependency Injection: Part 2

4.86/5 (38 votes)
23 Apr 2020CPOL5 min read 114.6K  
What is an Inversion of Control and how it resolves tight-coupling
This is the second part of a series of articles on Dependency Injection. It will give you an idea about what Inversion of Control is. There are other part of the article which explain about how to implement DIP.

Introduction

This is the continuation part of the article on Dependency Inversion Principle, IoC Container & Dependency Injection. In the previous part of this article, I explained what is Dependency Inversion Principle and its benefits. In this part of the article, I will be covering Inversion Of Control (IoC) and what is an IoC Container.

The following is the listing of different parts of the article:

Inversion of Control (IoC)

What is Inversion of Control

Many people have different opinions about Inversion of Control. You can have different meaning of it in different context of application. Basically, it is the pattern to invert the control of dependency by switching to another location who controls.

DIP says a high-level module should not depend on a low-level module and both high-level and low-level modules should depend on an abstraction. If you want to prevent the changes in high-level module on changing of low-level module, you need to invert the control so that low-level module will not control the interface and creation of objects that the high-level module needs. IoC is a pattern which provides that abstraction.

In the following example (derived from the previous part of the article), we are inverting the creation of dependency objects (reader and writer) by switching the code for creating dependency to service locator (marked in bold letters).

C#
public class Copy
{
    public void DoWork()
    {
        IReader reader = serviceLocator.GetReader();
        IWriter writer = serviceLocator.GetWriter();
        string data = reader.Read();
        writer.Write(data);
    }
}

This is one of the examples of Inversion Of Control. There can be various ways to invert the control. But in the current software development trends, there are 3 basic ways to invert the control.

Interface Inversion

This is a very common kind of inversion. I have discussed some about Interface Inversion in the first part of this article. By providing abstraction for all of our low-level modules doesn't mean that we have implemented DIP.

For example, we have two low-level modules, Car and Bicycle and both are having their own abstraction.

C#
public interface ICar
{
    void Run();
}
public class Car : ICar
{
    public void Run()
    {
        ....
    }
}

public interface IBicycle
{
    void Move();
}
public class Bicycle : IBicycle
{
    public void Move()
    {
        ....
    }
}

In the above example, although we have defined abstraction, still there is a problem. The problem is each of the low-level modules is having its own abstraction. So when high-level module interacts with the abstractions, high-level class has to treat each abstraction differently. For the above instance when high-level class holds instance of ICar, it needs to invoke Run() method whereas when it holds instance of IBicycle then it needs to invoke Move(). So in the above case, high-level class should have knowledge about the implementation of low-level module.

But Interface Inversion says the high-level module should define the abstraction, which should be followed by the low-level modules. The following example illustrates this:

C#
public interface IVehicle
{
    void Move();
}
public class Car : IVehicle
{
    public void Move()
    {
        ....
    }
}

public class Bicycle : IVehicle
{
    public void Move()
    {
        ....
    }
}

In the above example, both Car and Bicycle class implement IVehicle interface, which is defined by high-level class. In this case, the high-level class should not bother about the instance of which, it contains the reference. It just invokes Move() method with the help of interface.

Flow Inversion

Flow Inversion talks about inverting the execution flow. For example, earlier in DOS based programs, the program reads the value of each field sequentially. Until the value of first field is entered, you can't enter the value of the second field and subsequently for third and so on. The following is an example of console application written in C#.

C#
static void Main(string[] args)
{
    int nValue1, nValue2, nSum;

    Console.Write("Enter value1 :");
    nValue1 = Convert.ToInt32(Console.ReadLine());

    Console.Write("Enter value2 :");
    nValue2 = Convert.ToInt32(Console.ReadLine());

    nSum = nValue1 + nValue2;
    Console.WriteLine(string.Format
            ("The sum of {0} and {1} is {2}", nValue1, nValue2, nSum));

    Console.ReadKey();
}

Below is the output of the above program:

Image 1

Here, you can only enter the value of value2 only after giving value of value1. So entry of value2 is dependent on entry of value1.

But if you look at the following GUI application, you can see how we can invert the flow to remove dependency.

Image 2

Here, the execution flows are inverted and now input of both value1 and value2 are independent of each other.

Creation Inversion

In Creation Inversion, the creation of dependency object is inverted from high-level module to some other place.

C#
public class Copy
{
    public void DoWork()
    {
        IReader reader = serviceLocator.GetReader();
        IWriter writer = serviceLocator.GetWriter();
        string data = reader.Read();
        writer.Write(data);
    }
}

In the above program, we have inverted the creation of IReader and IWriter objects to serviceLocator. Object creation responsibility is given to serviceLocator. And Copy program doesn't have any idea about the instance. So for any addition of new implementation class of IReader and IWriter, there is no need of modifying Copy program.

IoC container also has other way to implement Creational Inversion. IoC container contains configuration settings for each abstraction and its implementation. Whenever there is a need of instance of an abstraction, IoC container provides that to the requester.

Image 3

In Figure 1.a, interface inversion is implemented but still there is a dependency, hence there is a tight-coupling between high-level and low-level modules. But in Figure 1.b, this dependency is resolved with the implementation of IoC container.

The mechanism of injecting dependency to the high-level module (consumer) is called as Dependency Injection (DI). DI uses IoC container to resolve the dependency.

Dependency Injection

As I mentioned above, DI is a mechanism of injecting dependency (low-level instance) to high-level module, and DI uses IoC container who holds the configuration settings related to dependency and resolves an abstraction upon request.

In .NET, there are many IoC containers which provides facilities to inject dependencies upon request. Few of the most popular IoC containers are listed below:

  • Unity Container
  • Castle Windsor
  • NInject
  • Structure Map

In the next part of the article, I will explain how to implement our own custom IoC container, and the implementation of Dependency Injection to resolve many issues related to layered based application.

Summary

In this part of the article, I have tried to explain what is an Inversion of Control and how it resolves tight-coupling. Hope you found this topic good and easy to understand.

History

  • 23rd April, 2020: Second revision (updated link to the final article)
  • 14th March, 2013: First revision

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)