C# – What does Dependency Injection mean by saying it can inject on runtime

cdependency-injection

I understand that the dependency injection is something done by coding and its all done at compile time. And that Dependency injection coding is easily done now by helper tools like Ninject.

However this is at compile time that the dependency injection occurs. Then what does it mean that dependency injection also occurs at Runtime?

This is straight from wiki about Dependency Injection

Dependency injection is a software design pattern that allows the removal of hard-coded dependencies and makes it possible to change them, whether at run-time or compile-time.

I mean I thought about it and came up with the idea that it may refer to multiple interfaces to a single class when a Delegate calls a metho from one of the interfaces, dependency injection decides something like this at runtime, but when I went deeper into it and even discussed it with my boss it sounded like its not that simple as that. And that then, how does the dependency injection makes this decision and assume it the correct one?

Anyone can help me clear out this question?

Best Answer

However this is at compile time that the dependency injection occurs.

The service type is defined at compile time. This is normally in the form of an interface or a (sometimes abstract) base class. The key point here is Liskovs substitution principle

It states that, in a computer program, if S is a subtype of T, then objects of type T may be replaced with objects of type S (i.e., objects of type S may be substituted for objects of type T) without altering any of the desirable properties of that program (correctness, task performed, etc.)

So we know the contract of the type at compile time

Then what does it mean that dependency injection also occurs at Runtime?

When implementing DI, for example, using an Inversion of Control container the implementation type is not known until run time.

Let's look at a basic example of making a cup of coffee using Simple Injector as the Inversion of Control container and NUnit to test the code.

Firstly we define the cup and our service type:

public class Cup 
{
    public List<string> ingredients = new List<string>();
}

public interface ICoffeeService
{
    Cup GetCoffee();
}

And an implementation of the service:

public class CoffeeService : ICoffeeService
{
    public Cup GetCoffee()
    {
        Cup cup = new Cup();
        cup.ingredients.Add("Coffee powder");
        cup.ingredients.Add("Hot water");
        return cup;
    }
}

Our test method configures the container to return the CoffeeService when asked for an instance of ICoffeeService

[Test]
public void CoffeeService_GetCoffee_ReturnsCupWithCoffeeAndHotWater()
{
    // Arrange
    var container = new SimpleInjector.Container();
    container.Register<ICoffeeService, CoffeeService>();

    // Act
    var coffeeService = container.GetInstance<ICoffeeService>();
    var cup = coffeeService.GetCoffee();

    // Assert
    Assert.That(cup.ingredients.Count == 2);
    Assert.That(cup.ingredients.Contains("Coffee powder"));
    Assert.That(cup.ingredients.Contains("Hot water"));
}

We decide we need to always add cream to our coffee but instead of changing CoffeeService we instead Decorate it with the new feature (Open/Closed principle).

The decorator

public class AddCreamDecorator : ICoffeeService
{
    private readonly ICoffeeService decorated;

    public AddCreamDecorator(ICoffeeService decorated)
    {
        this.decorated = decorated;
    }

    public Cup GetCoffee()
    {
        Cup cup = this.decorated.GetCoffee();
        cup.ingredients.Add("Cream");
        return cup;
    }
}

And the new test

[Test]
public void DecoratedCoffeeService_GetCoffee_CupWithCoffeeAndHotWaterAndCream()
{
    // Arrange
    var container = new SimpleInjector.Container();
    container.Register<ICoffeeService, CoffeeService>();
    container.RegisterDecorator(typeof(ICoffeeService), typeof(AddCreamDecorator));

    // Act
    var coffeeService = container.GetInstance<ICoffeeService>();
    var cup = coffeeService.GetCoffee();

    // Assert
    Assert.That(cup.ingredients.Count == 3);
    Assert.That(cup.ingredients.Contains("Coffee powder"));
    Assert.That(cup.ingredients.Contains("Hot water"));
    Assert.That(cup.ingredients.Contains("Cream"));
}

The most important point to take from this example is that we code to and compile against the service type ICoffeeService and the container gives us the configured runtime implementation. In test one we get an instance of CoffeeService while in test two we get an instance of CoffeeService that is decorated with an instance of AddCreamDecorator - in both cases the caller doesn't care as it get the service it needs.