Rob Cook


Home | Donate

Dependency injection explained


2022-10-09

Dependency injection (or DI as it is often abbreviated), is the act of passing the things a class depends on to it instead of letting that class source them itself. It is one way of achieving the Inversion of Control (or IoC) principle.

Let's take an example:


public class Foo
{
    public void DoStuff()
    {
        DoTheThing();
        DoTheOtherThing();
        
        var notifier = new Notifier();
        notifier.SendNotification("Blah");
    }
}

Here we have a class Foo, that uses an instance of class Notifier to send a notification. We say that Notifier is a dependency of Foo; Foo needs it to complete its work.

So what is wrong with this code?

How does DI help us here?

Let's apply DI to our earlier example:


public class Foo
{
    private readonly INotifier _notifier;
    
    public Foo(INotifier notifier)
    {
        _notifier = notifier
            ?? throw new ArgumentNullException(nameof(notifier));
    }
    
    public void DoStuff()
    {
        DoTheThing();
        DoTheOtherThing();
        _notifer.SendNotification("Blah");
    }
}

The first thing to observe is the notifier dependency is expressed as an interface. Classes should depend on abstractions not concrete types. We have made the INotifer dependency something that is passed to the class Foo instead of created by it. In this instance we have done it via the constructor. This is known as constructor injection.

It opens up a lot of flexibility for us as programmers. Firstly we can see from the constructor what dependencies this class has. Secondly we can vary the implementation we pass to the class, for example when we do unit testing, or as the result of some configuration option.

So what creates the INotifier instance that is passed to Foo? The client code that uses Foo is now responsible for that. So haven't we just moved the problem? No. The client code is free to define it's dependencies via injection as well.

Taken to its logical conclusion then, the entry point to your program is now responsible for newing up all the dependencies in your program. This point in your code is known as a composition root.

Manually newing up all this can be tedious and error prone. It is here that the use of a dependency injection container is useful. A container allows you to register dependencies with it, and takes over the role of newing up things as they are requested.

Asp.Net Core is a good example of this in the .NET world. The container is configured at application startup. Dependencies are passed into your controller instances via their constructor, and so on down the classes.

DI is a very common technique in static languages. It allows great flexibility in configuring the behavior of your application. It also helps us to unit test our code.

end