當我們想要讓物件延遲實例化時,可以透過 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
範例位置
若有謬誤,煩請告知,新手發帖請多包涵
Microsoft MVP Award 2010~2017 C# 第四季
Microsoft MVP Award 2018~2022 .NET