c# - .NET:如何提供孙依赖的多个实现来创建两个不同的子依赖实例?
问题描述
我希望一个应用程序(假设是一个控制台应用程序)让它的主服务两次使用相同的依赖项(同一接口的两个不同实现)。我读过这样的链接( “使用委托选择特定接口实现”部分),它们提供了一个工厂函数来生成多个实现。但是,我需要将这个问题降低一级。
依赖对象实际上是相同的实现,只是它有自己的依赖对象,这些依赖对象会有所不同。对象图是这样的:
A (main service)
-> B (child dependency)
-> C_1 (grandchild dependency, implementation 1)
-> B (same child dependency, second instance)
-> C_2 (grandchild dependency, implementation 2)
我试图弄清楚如何使用构造函数注入来连接所有这些。这是一些示例代码:
class Program
{
static async Task Main( string[] args )
{
await Host
.CreateDefaultBuilder( args )
.ConfigureServices( services =>
{
new ServicesInstaller().AddServices(services);
})
.RunConsoleAsync();
}
}
}
public class ServicesInstaller
{
public void AddServices( IServiceCollection services )
{
// What do I do here to give two different implementations of C to two different
// instances of B, so that A can use each instance of B separately?
services.AddSingleton<IB, B>();
services.AddSingleton<IC, C_Impl_1>();
services.AddHostedService<A>();
}
}
public interface IB
{
void Do_B_Stuff();
}
public interface IC
{
void Do_C_Stuff();
}
public class A : IHostedService
{
private readonly IB b1;
private readonly IB b2;
private readonly IHostApplicationLifetime hostLifetime;
// b1 needs to be class B that has a dependency on C_Impl_1
// b2 needs to be class B that has a dependency on C_Impl_2
public A(IB b1, IB b2, IHostApplicationLifetime hostApplicationLifetime )
{
this.b1 = b1;
this.b2 = b2;
hostLifetime = hostApplicationLifetime;
}
public async Task StartAsync( CancellationToken cancellationToken )
{
b1.Do_B_Stuff();
b2.Do_B_Stuff();
await Task.Delay(1000);
// Quit.
hostLifetime.StopApplication();
}
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}
实现:
public class B : IB
{
private readonly IC c;
public B(IC c) => this.c = c;
public void Do_B_Stuff() => c.Do_C_Stuff();
}
public class C_Impl_1 : IC
{
public void Do_C_Stuff() => Console.WriteLine( "implementation #1");
}
public class C_Impl_2 : IC
{
public void Do_C_Stuff() => Console.WriteLine("implementation #2");
}
解决方案
Microsoft.Extensions.DependencyInjection (MS.DI) 库不包含用于实现条件绑定的功能。这意味着您必须恢复到旧方法,即:注册代表:
services.AddSingleton<C_Impl_1>();
services.AddSingleton<C_Impl_2>();
services.AddSingleton<IHostedService>(c =>
new A(
b1: new B(c.GetRequiredService<C_Impl_1>()),
b2: new B(c.GetRequiredService<C_Impl_2>()),
hostApplicationLifetime:
c.GetRequiredService<IHostApplicationLifetime>())
或者,如果您希望构造函数或其中一个A
或B
随时间变化,并且您希望能够使用自动连线,则可以使用该ActivatorUtilities.CreateInstance
方法。它启用自动接线:
services.AddSingleton<C_Impl_1>();
services.AddSingleton<C_Impl_2>();
services.AddSingleton<IHostedService>(c =>
ActivatorUtilities.CreateInstance<A>(c,
ActivatorUtilities.CreateInstance<B>(c, c.GetRequiredService<C_Impl_1>()),
ActivatorUtilities.CreateInstance<B>(c, c.GetRequiredService<C_Impl_2>())
)
);
更新:
您是否知道其他支持解决此类问题的 DI 框架?
正如我在评论中指出的那样,对于 DI 容器,您的问题实际上很棘手,因为B
实际上并非如此Singleton
(因为有两个实例)。这会混淆容器,即使是那些可以基于祖父母进行有条件注册的容器。我唯一有经验可以谈论的容器是 Simple Injector(我维护)。以下是如何使用 Simple Injector 实现此功能的示例:
container.RegisterSingleton<IHostedService, A>();
container.RegisterConditional<IB, B<C_Impl_1>>(
Lifestyle.Singleton,
c => c.Consumer.Target.Name == "b1");
container.RegisterConditional<IC, C_Impl_1>(
Lifestyle.Singleton,
c => c.Consumer.ImplementationType == typeof(B<C_Impl_1>));
container.RegisterConditional<IB, B<C_Impl_2>>(
Lifestyle.Singleton,
c => c.Consumer.Target.Name == "b2");
container.RegisterConditional<IC, C_Impl_2>(
Lifestyle.Singleton,
c => c.Consumer.ImplementationType == typeof(B<C_Impl_2>));
上面的注册依赖于一个新类型,即B<T>
,它派生自B
并可以放置在您的Composition Root中:
class B<T> : B where T : IC
{
public B(IC c) : base(c) { }
}
此类型可用于区分存在的两个版本B
(一个B
带有 anC_Impl_1
和第二个取决于C_Impl_2
)。这种新的泛型类型可以消除混淆。
您将得到以下对象图:
new A(
b1: new B<C_Impl_1>(new C_Impl_1()),
b2: new B<C_Impl_2>(new C_Impl_2()),
hostApplicationLifetime: new HostApplicationLifetime>());
注意:在 Simple Injector 中有更紧凑的方法来定义此对象图,但这种特定的连接方式允许 Simple Injector 自动连接 、 、 和 中的所有其他依赖项,同时
A
允许容器分析、验证和诊断您的对象图。B
C_Impl_1
C_Impl_2
这种方法有以下缺点:
- 需要定义一个 new
B<T>
,它仅用于简化容器中的注册(提示:这个技巧也可能适用于其他 DI 容器)。 - 注册取决于
A
构造函数参数的确切命名(但至少 Simple Injector 的验证功能会立即发现不匹配)。
推荐阅读
- html - 如何在 Vue Cli 生成的 WPA 项目的标题内编辑自动注入的网站图标标签?
- docker - 如何使用 docker 将 metricbeat 连接到 elasticsearch 和 kibana
- c++ - 如果输入偶数,C++ 中的显示问题
- html - vue,如何以编程方式动态地单击将组件添加到 DOM 特定位置?
- python - Python - 从子字符串列表中搜索列表中的子字符串
- ansible - Ansible 更新 yum 人性化输出
- c# - 比较坐标时无法从 int 转换为 System.Drawing.Point
- java - Swing 分层 - 透明组件忽略底层 AWT 元素
- php - PHP中POST Sanitation和Prepared语句之间的区别
- scala - Scala Left Join 返回 Full Join 的结果