首页 > 解决方案 > .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");
}

标签: c#.netdependency-injection

解决方案


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>())

或者,如果您希望构造函数或其中一个AB随时间变化,并且您希望能够使用自动连线,则可以使用该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允许容器分析、验证和诊断您的对象图。BC_Impl_1C_Impl_2

这种方法有以下缺点:

  • 需要定义一个 new B<T>,它仅用于简化容器中的注册(提示:这个技巧也可能适用于其他 DI 容器)。
  • 注册取决于A构造函数参数的确切命名(但至少 Simple Injector 的验证功能会立即发现不匹配)。

推荐阅读