Delaying injected dependency instantiation

Using Lazy to delay the instantiation of injected dependencies

Home DailyDrop

Daily Knowledge Drop

The Lazy class can be leveraged to delay instantiation of implementations injected via dependency injection. This is especially useful if the instantiation is high cost (it has a nigh number of dependencies) and the functionality of the dependency is not used in every code path.

We have previously looked at the Lazy class, but not how its functionality can be used in conjunction with dependency injection, covered by this post.


Methods

Normal injection

In the examples below, we have a IDatabaseAccess interface, and a SqlDatabaseAccess implementation:

// basic interface
public interface IDatabaseAccess 
{
    void PerformDatabaseUpdate();
}

// the SQL implementation of the interface
public class SqlDatabaseAccess : IDatabaseAccess 
{
    public SqlDatabaseAccess()
    {
        Console.WriteLine($"In constructor of '{nameof(SqlDatabaseAccess)}'");
    }

    public void PerformDatabaseUpdate()
    {
        Console.WriteLine($"Database update performed");
    }
}

We also have an endpoint, where only on some paths is the database accessed:

app.MapGet("/maybe-do-work", (IDatabaseAccess databaseAccess) =>
{
    // perform a random check
    var rando = new Random();
    if(rando.Next(10) > 5)
    {
         databaseAccess.PerformDatabaseUpdate();
    }

    return "'maybe-do-work' endpoint successfully called";
});

With the dependency injection container setup as follows:

builder.Services.AddTransient<IDatabaseAccess, SqlDatabaseAccess>();

The problem here is that the IDatabaseAccess implementation is always initialized even if not used. This might be fine if the implementation construction is low cost, but if the IDatabaseAccess implementation is high cost, and and itself has many dependencies, this could have an impact on performance.

Let's look at how the Lazy class can be used to achieve a delayed instantiation.


Lazy injection

To leverage the Lazy class, the first step involves setting up the dependency injection container with Lazy<IDatabaseAccess>:

builder.Services.AddTransient<IDatabaseAccess, SqlDatabaseAccess>();
builder.Services.AddTransient<Lazy<IDatabaseAccess>>(
    provider =>
    {
        Console.WriteLine("In 'Lazy<IDatabaseAccess>' implementation factory");

        // be sure to use the "valueFactory" constructor and 
        // NOT the "value" constructor
        return new Lazy<IDatabaseAccess>(
            () => provider.GetRequiredService<IDatabaseAccess>());
    });

And then injecting Lazy<IDatabaseAccess> into the delegate, instead of IDatabaseAccess:

app.MapGet("/maybe-do-work-lazy", (Lazy<IDatabaseAccess> databaseAccess) =>
{
    var rando = new Random();

    if (rando.Next(10) > 5)
    {
        databaseAccess.Value.PerformDatabaseUpdate();
    }

    return "'maybe-do-work-lazy' endpoint successfully called";
});

Executing the code and calling the endpoint, results in the following (example output):

In Lazy<IDatabaseAccess> implementation factory
In Lazy<IDatabaseAccess> implementation factory
In Lazy<IDatabaseAccess> implementation factory
In constructor of 'SqlDatabaseAccess'
Database update performed

As we can see, SqlDatabaseAccess is only initialized when it is being used - exactly what we are after.


ServiceProvider injection

Another option is to inject IServiceProvider directly and get the service only when required - however this is considered by some to be an anti-pattern.

The above example is updated to look as follows:

app.MapGet("/maybe-do-work-provider", (IServiceProvider provider) =>
{
    Console.WriteLine("In `maybe-do-work-provider` endpoint");
    var rando = new Random();

    if (rando.Next(10) > 5)
    {
        var databaseAccess = provider.GetRequiredService<IDatabaseAccess>();
        databaseAccess.PerformDatabaseUpdate();
    }

    return "'maybe-do-work-provider' endpoint successfully called";
});

And calling the endpoint:

In `maybe-do-work-provider` endpoint
In `maybe-do-work-provider` endpoint
In constructor of 'SqlDatabaseAccess'
Database update performed
In `maybe-do-work-provider` endpoint
In `maybe-do-work-provider` endpoint

Again, SqlDatabaseAccess is only initialized when it is being used.


Notes

A very useful technique to use when the instantiation of an object has a large overhead, but it not used in all code paths. The constructor in general should be kept as quick and performant as possible, but sometimes its unavoidable to have to use a high cost constructor (if using a 3rd party package, for example).


References

Delayed Instantiation Using Dependency Injection In .NET

Daily Drop 168: 26-09-2022

At the start of 2022 I set myself the goal of learning one new coding related piece of knowledge a day.
It could be anything - some.NET / C# functionality I wasn't aware of, a design practice, a cool new coding technique, or just something I find interesting. It could be something I knew at one point but had forgotten, or something completely new, which I may or may never actually use.

The Daily Drop is a record of these pieces of knowledge - writing about and summarizing them helps re-enforce the information for myself, as well as potentially helps others learn something new as well.
c# .net dependencyinjection lazy DI