在 .NET 的 Dependency Injection Container 中使用延遲實例化

當我們想要讓物件延遲實例化時,可以透過 Lazy<T> + Delegate 來實現這件事,同樣的在 DI Container 也可以套件,無意間發現 Lazy Proxy 這個套件,可以簡化配置 Lazy 的配置,立馬收入武器內

開發環境

  • Windows 11
  • ASP.NET 7
  • Rider 2023.2

實作

物件依賴關係如下圖

程式碼如下

namespace Lab.MsDI.Lazy;

public interface IService
{
    string Get();
}

public class Service : IService
{
    private readonly IServiceA _serviceA;
    private readonly IServiceB _serviceB;

    public Service(IServiceA serviceA,
        IServiceB serviceB)
    {
        this._serviceA = serviceA;
        this._serviceB = serviceB;
    }

    public string Get()
    {
        var random = new Random().Next(1, 10);
        if (random % 2 == 0)
        {
            return this._serviceB.Get();
        }

        return this._serviceA.Get();
    }
}

public interface IServiceA
{
    string Get();
}

public class ServiceA : IServiceA
{
    public ServiceA() => Console.WriteLine("Create instance for ServiceA");

    public string Get() => "ServiceA";
}

public interface IServiceB
{
    string Get();
}

public class ServiceB : IServiceB
{
    public ServiceB() => Console.WriteLine("Create instance for ServiceB");

    public string Get() => "ServiceB";
}

 

配置 DI Container

builder.Services.AddScoped<IServiceA, ServiceA>();
builder.Services.AddScoped<IServiceB, ServiceB>();
builder.Services.AddScoped<IService, Service>();

 

Web API 依賴 IService

[ApiController]
[Route("[controller]")]
public class DemoController : ControllerBase
{
    private readonly ILogger<DemoController> _logger;
    private readonly IService _service;
    public DemoController(ILogger<DemoController> logger, IService service)
    {
        this._logger = logger;
        this._service = service;
    }

    [HttpGet(Name = "GetDemo")]
    public ActionResult Get()
    {
        return this.Ok(this._service.Get());
    }
}

 

觀察一下執行狀況,可以發現 Service.Get 方法雖然只有使用 ServiceA 或者 ServiceA 但 ServiceA、ServiceB 都已經實例化了

 

我想要有使用時才實例化,改用 Lazy<T>,程式碼如下

public class ServiceLazy : IService
{
    private readonly Lazy<IServiceA> _serviceA;
    private readonly Lazy<IServiceB> _serviceB;

    public ServiceLazy(Lazy<IServiceA> serviceA,
        Lazy<IServiceB> serviceB)
    {
        this._serviceA = serviceA;
        this._serviceB = serviceB;
    }

    public string Get()
    {
        var random = new Random().Next(1, 10);
        if (random % 2 == 0)
        {
            return this._serviceB.Value.Get();
        }

        return this._serviceA.Value.Get();
    }
}

 

DI Container 配置方式如下

builder.Services.AddScoped<IServiceA, ServiceA>();
builder.Services.AddScoped<IServiceB, ServiceB>();

builder.Services.AddScoped<IService>(p =>
{
    var serviceA = new Lazy<IServiceA>(() => p.GetService<IServiceA>());
    var serviceB = new Lazy<IServiceB>(() => p.GetService<IServiceB>());
    return new ServiceLazy(serviceA, serviceB);
});

 

再觀察一次,這次就變成只有實例化 ServiceB,沒有用到的 ServiceA 就不會實例化

 

每一次都要自己手動處理 Lazy 有點麻煩

  • 依賴關係要改用 Lazy
  • DI Container 也要配置 Lazy 注入

LazyProxy 套件可以讓我們不改變依賴的合約,只需要在 DI Container 配置 Lazy 的設定即可。

dotnet add package LazyProxy --version 1.0.2

 

使用 LazyProxyBuilder 來配置

builder.Services.AddScoped<IServiceA, ServiceA>();
builder.Services.AddScoped<IServiceB, ServiceB>();

builder.Services.AddScoped<IService>(p =>
{
    var lazyProxy = LazyProxyBuilder.CreateInstance<IService>(() =>
    {
        var serviceA = p.GetService<IServiceA>();
        var serviceB = p.GetService<IServiceB>();
        return new Service(serviceA, serviceB);
    });
    return lazyProxy;
})

 

作者還有針對 IServiceCollection 的擴充套件

dotnet add package LazyProxy.ServiceProvider --version 0.1.3

 

把需要 Lazy 的物件通通都使用 AddLazy 前贅詞配置 DI Container

builder.Services.AddLazyScoped<IServiceA, ServiceA>();
builder.Services.AddLazyScoped<IServiceB, ServiceB>();
builder.Services.AddLazyScoped<IService, Service>();

 

這樣一來的效果就跟上述一樣

除了微軟的 Dependency Injection Container 也支援了 Unity、Autofac

範例位置

sample.dotblog/DI/Lab.MsDI/Lab.MsDI.Lazy at 67272bbf6711efb59c791d42640e01c59c3e1304 · yaochangyu/sample.dotblog (github.com)

若有謬誤,煩請告知,新手發帖請多包涵


Microsoft MVP Award 2010~2017 C# 第四季
Microsoft MVP Award 2018~2022 .NET

Image result for microsoft+mvp+logo