Constructor Injection

A class asks for its dependencies in its constructor and keeps them in readonly fields. Whoever creates it passes them in, so a test can pass a fake.

Journeyman · C# · OOP · Updated

After this lesson you can

  • Inject a dependency through a constructor and keep it in a readonly field
  • Pass a fake dependency in a test, so the result doesn't depend on the outside world

Key takeaways

  • With constructor injection, a class asks for its dependencies as constructor parameters instead of creating them with new.
  • Keep an injected dependency in a readonly field, so it never changes once the constructor sets it.
  • In a test, pass a fake such as new FixedClock(9), so the result is the same at any time of day.
  • ProductDao asking for its connection string is dependency injection too: the app or the test decides which database it uses.
  • ASP.NET Core has a built-in container that creates controllers and passes in what their constructors ask for, such as IConfiguration.

Dependency injection means a class receives its dependencies instead of creating them. The most common way is through the constructor: the class asks for what it needs, as parameters, and keeps them.

Asking for the clock

Here's the Greeter from the last lesson, rewritten to ask for its clock:

Greeter greeter = new Greeter(new Clock());
Console.WriteLine(greeter.Greet("Ada"));

public class Clock
{
    public virtual int Hour => DateTime.Now.Hour;
}

public class Greeter
{
    private readonly Clock clock;

    public Greeter(Clock clock)
    {
        this.clock = clock;
    }

    public string Greet(string name)
    {
        return clock.Hour < 12 ? quot;Good morning, {name}!" : quot;Good afternoon, {name}!";
    }
}

Three small changes:

  1. The field has no new. Greeter no longer decides which clock it gets.
  2. The constructor asks for a Clock and keeps it in the field.
  3. The field is readonly: once the constructor sets it, it never changes.

The code that creates the greeter, here new Greeter(new Clock()), now makes the decision.

A fake clock for testing

Because Hour is virtual, a test can pass a fake clock that always says the same time. A fake is a stand-in that behaves predictably:

Greeter morning = new Greeter(new FixedClock(9));
Greeter afternoon = new Greeter(new FixedClock(15));

Console.WriteLine(morning.Greet("Ada"));
Console.WriteLine(afternoon.Greet("Ada"));

public class Clock
{
    public virtual int Hour => DateTime.Now.Hour;
}

// A fake clock for tests: it's always the hour it was given.
public class FixedClock : Clock
{
    private readonly int hour;

    public FixedClock(int hour)
    {
        this.hour = hour;
    }

    public override int Hour => hour;
}

public class Greeter
{
    private readonly Clock clock;

    public Greeter(Clock clock)
    {
        this.clock = clock;
    }

    public string Greet(string name)
    {
        return clock.Hour < 12 ? quot;Good morning, {name}!" : quot;Good afternoon, {name}!";
    }
}
Good morning, Ada!
Good afternoon, Ada!

Now both greetings can be tested at any time of day, and every run gives the same answer. That's polymorphism at work: Greeter holds a Clock, and the object behind it can be the real clock or a fixed one. (In Abstraction and Interfaces, you'll meet a cleaner way to define what a clock must do.)

A test looks like this:

[TestMethod]
public void Greet_BeforeNoon_SaysGoodMorning()
{
    Greeter greeter = new Greeter(new FixedClock(9));

    string greeting = greeter.Greet("Ada");

    Assert.AreEqual("Good morning, Ada!", greeting);
}

You've already used it

The connection string has been injected all along. ProductDao asks for it in its constructor:

public ProductDao(string connectionString)
{
    this.connectionString = connectionString;
}

So the app decides which database the DAO uses, and the exercise's tests decided for themselves: they passed their own connection string, which could point at any database, without a change to the DAO. And in ASP.NET Core MVC, ProductsController asked for IConfiguration in its constructor, and ASP.NET Core passed it in. That's dependency injection too: ASP.NET Core has a built-in container that creates controllers and hands them what they ask for. ASP.NET Core MVC in Depth shows how to register your own classes with it.

Check yourself

2 questions to try on the lesson page.

So what

Ask, don't make: a class that asks for its dependencies in its constructor can be given the real ones in the app and fakes in the tests. That one habit makes code testable, and it's how every ASP.NET Core app is built.

Key terms

Cheat sheets