67

I am trying to use the generic Lazy class to instantiate a costly class with .net core dependency injection extension. I have registered the IRepo type, but I'm not sure what the registration of the Lazy class would look like or if it is even supported. As a workaround I have used this method http://mark-dot-net.blogspot.com/2009/08/lazy-loading-of-dependencies-in-unity.html

config:

public void ConfigureService(IServiceCollection services)
{
    services.AddTransient<IRepo, Repo>();
    //register lazy
}

controller:

public class ValuesController : Controller 
{
    private Lazy<IRepo> _repo;

    public ValuesController (Lazy<IRepo> repo)
    {
        _repo = repo;
    }

    [HttpGet()]
    public IActionResult Get()
    {
         //Do something cheap
         if(something)
             return Ok(something);
         else
             return Ok(repo.Value.Get());
    }
}
Nkosi
  • 235,767
  • 35
  • 427
  • 472
Alex Terry
  • 1,992
  • 1
  • 11
  • 17
  • 6
    "to instantiate a costly class". Why is that class costly to create? Classes should _not_ be costly to create. Their constructors should be [simple](http://blog.ploeh.dk/2011/03/03/InjectionConstructorsshouldbesimple/), fast and [reliable](http://blog.ploeh.dk/2011/03/04/Composeobjectgraphswithconfidence/). – Steven Jul 05 '17 at 20:20
  • 4
    No (repository/context/...) class should open the connection in its constructor. – H H Jul 05 '17 at 21:37
  • 13
    It is costly for means I can't control. – Alex Terry Jul 05 '17 at 23:29

9 Answers9

108

Here's another approach which supports generic registration of Lazy<T> so that any type can be resolved lazily.

services.AddTransient(typeof(Lazy<>), typeof(Lazier<>));

internal class Lazier<T> : Lazy<T> where T : class
{
    public Lazier(IServiceProvider provider)
        : base(() => provider.GetRequiredService<T>())
    {
    }
}
Michael Petito
  • 12,891
  • 4
  • 40
  • 54
  • 1
    Lazy is unnecessarily heavy, I would recommend lighter version of it. – Akash Kava Mar 01 '18 at 05:46
  • @AkashKava Which lighter alternative do you mean? – MarkusSchaber Apr 05 '18 at 08:41
  • 1
    @MarkusSchaber `Lazy` has locks and way lot of code, you can easily write Lighter Lazy class. – Akash Kava Apr 05 '18 at 15:06
  • 43
    @Akash please show how you can "easily" write a "lighter" `Lazy` class that has thread-safety and does its job as well as the built-in type. I think it's safe to say that `Lazy` performs well enough for 99.9% of its use cases. – CodeCaster Apr 06 '18 at 08:50
  • 6
    You could write your own but this is a concise solution. My assumption is the costly initialization of T outweighs any costs associated with Lazy. – Michael Petito Apr 06 '18 at 11:44
  • 15
    This is a good solution, till someone writes the `Laziest` – Jonesopolis Oct 29 '20 at 19:19
  • @MichaelPetito Not sure why you'd want this to be `Transient` lifetime. You'll end up creating it multiple times which defeats most of the benefit of `Lazy` – Dave Black Jan 20 '22 at 21:49
  • 1
    @DaveBlack I suppose it depends on whether the lazily created instance should be shared. The benefit of lazy is that the creation of the instance is deferred, whereas the lifetime options will control whether the created instance is shared. – Michael Petito Jan 22 '22 at 15:52
  • 1
    In terms of thread safety, `.GetRequiredService()` already implements the requirements of `Lazy`. You could simplify your `Lazier<>` class to a single property `public T Value => _value ?? _value = provider.GetRequiredService();`. Which is already how `IOptions` works. Just move your heavy initialisation to a `IConfigureOptions` service. – Jeremy Lakeman Nov 29 '22 at 01:43
  • 1
    @JeremyLakeman Could you post a dedicated answer and elaborate more? – user764754 Mar 08 '23 at 10:30
27

You only need to add a registration for a factory method that creates the Lazy<IRepo> object.

public void ConfigureService(IServiceCollection services)
{
    services.AddTransient<IRepo, Repo>();
    services.AddTransient<Lazy<IRepo>>(provider => new Lazy<IRepo>(provider.GetService<IRepo>));
}
bubbleking
  • 3,329
  • 3
  • 29
  • 49
Scott Chamberlain
  • 124,994
  • 33
  • 282
  • 431
3

A bit late to the party here, but to throw another solution on to the pile... I wanted to selectively allow Lazy instantiation of services, so I created this extension method:

public static IServiceCollection AllowLazy(this IServiceCollection services)
{
    var lastRegistration = services.Last();
    var serviceType = lastRegistration.ServiceType;

    // The constructor for Lazy<T> expects a Func<T> which is hard to create dynamically.
    var lazyServiceType = typeof(Lazy<>).MakeGenericType(serviceType);

    // Create a typed MethodInfo for `serviceProvider.GetRequiredService<T>`,
    // where T has been resolved to the required ServiceType
    var getRequiredServiceMethod = typeof(ServiceProviderServiceExtensions).GetMethod(
        nameof(ServiceProviderServiceExtensions.GetRequiredService),
        1,
        new[] { typeof(IServiceProvider) }
    );

    var getRequiredServiceMethodTyped = getRequiredServiceMethod?.MakeGenericMethod(serviceType);

    // Now create a lambda expression equivalent to:
    //
    //     serviceProvider => serviceProvider.GetRequiredService<T>();
    //
    var parameterExpr = Expression.Parameter(typeof(IServiceProvider), "serviceLocator");
    var lambda = Expression.Lambda(
        Expression.Call(null, getRequiredServiceMethodTyped!, parameterExpr),
        parameterExpr
    );

    var lambdaCompiled = lambda.Compile();

    services.Add(new ServiceDescriptor(lazyServiceType,
        serviceProvider => Activator.CreateInstance(lazyServiceType, lambdaCompiled.DynamicInvoke(serviceProvider))!,
        lastRegistration.Lifetime));

    return services;
}

That way you can just tack .AllowLazy() on to your registration without having to re-specify the types or the scope -

public void ConfigureService(IServiceCollection services)
{
    services.AddTransient<IRepo, Repo>().AllowLazy();
}
gerrod
  • 6,119
  • 6
  • 33
  • 45
2

To register services as lazy

services.AddScoped<Lazy<AService>>();
services.AddScoped<Lazy<BService>>();

Or by creating an extension

static class LazyServiceCollection
{
    public static void AddLazyScoped<T>(this IServiceCollection services)
    {
        services.AddScoped<Lazy<T>>();
    }
}

...

services.AddLazyScoped<AService>();
services.AddLazyScoped<BService>();

And use it

[ApiController, Route("lazy")]
public class LazyController : ControllerBase
{
    private readonly Lazy<AService> _aService;
    private readonly Lazy<BService> _bService;

    public LazyController(Lazy<AService> aService, Lazy<BService> bService)
    {
        _aService = aService;
        _bService = bService;
    }

    [HttpGet("a")]
    public ActionResult GetA()
    {
        _aService.Value.DoWork();
        return new OkResult();
    }

    [HttpGet("b")]
    public ActionResult GetB()
    {
        _bService.Value.DoWork();
        return new OkResult();
    }
}

Result

Init AService
AService Work
Edwin Sulaiman
  • 639
  • 9
  • 14
1

Services that are to be fetched in Lazy will be re-introduced by the factory registration method with the new Lazy of the intended service type and provided for its implementation using serviceProvider.GetRequiredService.

services.AddTransient<IRepo, Repo>()
        .AddTransient(serviceProvider => new Lazy<IRepo>(() => serviceProvider.GetRequiredService<IRepo>()));
Reza Jenabi
  • 3,884
  • 1
  • 29
  • 34
1

To my opinion, the code below should do the work(.net core 3.1)

services.AddTransient<IRepo, Repo>();
services.AddTransient(typeof(Lazy<>), typeof(Lazy<>));
misha130
  • 5,457
  • 2
  • 29
  • 51
Avi Siboni
  • 686
  • 7
  • 16
1

I use this form, I hope resolve your problem, this code for Scoped and Transient Life-Cycle you can write for other Life-Cycle look like this.

My Dot-Net-Core vertion is 6

public static class Lazier
{
    public static IServiceCollection AddScopedLazier<T>(this IServiceCollection services)
    {
        return services.AddScoped(provider => new Lazy<T>(provider.GetService<T>));
    }
    public static IServiceCollection AddTransientLazier<T>(this IServiceCollection services)
    {
        return services.AddTransient(provider => new Lazy<T>(provider.GetService<T>));
    }
}

Usage for Scoped Life-Cycle:

services.AddScoped<ISideDal, EfSideDal>().AddScopedLazier<ISideDal>();

Usage for Transient Life-Cycle:

services.AddTransient<ISideDal, EfSideDal>().AddTransientLazier<ISideDal>();
1

I'm a fan of the accepted answer above, and upvoted that. However, reading Jeremy Lakeman's comment

"In terms of thread safety, .GetRequiredService() already implements the requirements of Lazy. You could simplify your Lazier<> class to a single property public T Value => _value ?? _value = provider.GetRequiredService();. Which is already how IOptions works. Just move your heavy initialisation to a IConfigureOptions service."

Here's what I did to avoid the overhead of the thread safety in Lazy<>, which we don't need for the services we're working with here:

public class SimpleLazy<T> where T : class
{
    private readonly Func<T> _valueFactory;
    private T? _value;
    // This constructor is used by the Dependency Injection.
    public SimpleLazy(IServiceProvider serviceProvider)
    {
        _valueFactory = () => serviceProvider.GetRequiredService<T>();
    }
    public T Value
    {
        get
        {
            if (_value == null)
                _value = _valueFactory();
            return _value;
        }
    }
}

In Program.cs I register SimpleLazy<> as a service like this, next to the other services I register:

    builder.Services.AddTransient(typeof(SimpleLazy<>));
    builder.Services.AddScoped<IEmailSender, EmailSender>();

In controllers and other places where dependencies are injected, I ask for the service to get injected like this (with "IEmailService" being an example service I lazy-fy in some cases)

public RotationController(IHttpContextAccessor httpContextAccessor,
    SimpleLazy<IEmailSender> lazyEmailSender, 
    ILogger<OncallController> logger, 
    oncallDBContext oncallDBContext)

And then use the injected lazy-fied services like this:

        await lazyEmailSender.Value.QueueAdminErrorEmail(description, 
           targetEmailAddresses, 
           errorText, 
           new EmailSentCallback(RemoveNotificationFromQueue));
  • As an aside, as I've been working with this further, I had to add a 2nd constructor to my SimpleLazy class, to make it simple to mock in test cases: // Used by test cases to mock SimpleLazy and classes it is used with. public SimpleLazy() { _valueFactory = () => throw new Exception("SimpleLazy.ValueFactory not set"); } With that constructor, I can mock IEmailSender like this: Mock> lazyMockEmailSender = new Mock>(); lazyMockEmailSender.Setup(x => x.Value).Returns(new Mock().Object); – Kristian Andaker Jun 17 '23 at 20:07
1

Maybe I'm late to the party, but I developed a solution using the best of both worlds from Michael Perito and gerrod answers:

Lazy Service:

public class LazyService<T> : Lazy<T> where T : class
{
    public LazyService(IServiceScopeFactory scopeFactory)
        : base(() =>
        {
            var scope = scopeFactory.CreateScope();
            return scope.ServiceProvider.GetRequiredService<T>();
        })
    {
    }
}

I'm using IServiceScopeFactory to prevent Cannot access a disposed object. Object name: 'IServiceProvider'. exceptions at runtime.

The extension to add the LazyService:


public static class ServiceCollectionExtensions
{
    public static IServiceCollection AllowLazyInitialization(this IServiceCollection services) 
    {
        var lastRegistration = services.Last();
        
        var lazyServiceType = typeof(Lazy<>).MakeGenericType(
            lastRegistration.ServiceType);
        
        var lazyServiceImplementationType = typeof(LazyService<>).MakeGenericType(
            lastRegistration.ServiceType);
        
        services.Add(new ServiceDescriptor(lazyServiceType, lazyServiceImplementationType,lastRegistration.Lifetime));
        return services;
    }
}

Usage:

        services.AddTransient<GridViewFactory>().AllowLazyInitialization();

Complete example:

Implementation and usage of a lazy service to prevent a circular dependency.

Benjamin Brandt
  • 367
  • 3
  • 13