IoC 容器自动装配一个类型的多个实例(在 .NET 中)

IoC container auto-wiring with multiple instances of a type (in .NET)

我有一个应用程序使用依赖注入,但目前没有使用任何 IoC 容器。我目前在我的应用程序设置代码中有如下内容:

ISimpleDependency ez = new SimpleDependency();
ISomeOtherDependency otherDep = new SomeOtherDependency();

FooConfig fooConfigA = Settings.Default.FooConfigA;
FooConfig fooConfigB = Settings.Default.FooConfigB;

IFoo fooA = new Foo(fooConfigA, ez);
IFoo fooB = new Foo(fooConfigB, ez);

Bar bar = new Bar(fooA, otherDep);
Baz baz = new Baz(fooB, ez, otherDep);
Qux qux = new Qux(fooA, fooB); //params IFoo[] constructor to which we want to pass every Foo

为了降低我的应用设置代码的复杂度并提高可维护性,我想引入一个 IoC 容器。有什么方法可以在这里使用 IoC 容器来自动连接所有内容,而无需将 Foo/Bar/Baz/Qux 类 与 IoC 容器实现的选择紧密耦合?

您最终会得到耦合到 IOC 容器的设置代码,但这没关系。关键是设置始终发生在应用程序的组合根中。或者换句话说,依赖关系是在应用程序启动时在使用 classes 之前指定的。 classes 本身将在不知道容器或如何创建依赖项的情况下运行。

假设这些是您的 Foo classes 和接口:

public interface IFoo { }

public interface IFooConfig { }

public class Foo : IFoo
{
    private readonly IFooConfig _config;

    public Foo(IFooConfig config)
    {
        _config = config;
    }
}

public class FooConfigA : IFooConfig { }

public class FooConfigB : IFooConfig { }

这是一些容器代码。如您所见,它变得复杂了。如果您的依赖关系小而简单,则可能不值得。

以温莎为例,您的设置可能如下所示。有不止一种方法可以做到这一点,所以我会把它留给你来决定它是更简单还是更可取。

container.Register(
    Component.For<IFooConfig, FooConfigA>().Named("FooConfigA"),
    Component.For<IFooConfig, FooConfigB>().Named("FooConfigB"),
    Component.For<IFoo, Foo>()
        .DependsOn(Dependency.OnComponent<IFooConfig, FooConfigA>()).Named("FooA")
        .IsFallback(),
    Component.For<IFoo, Foo>()
        .DependsOn(Dependency.OnComponent<IFooConfig, FooConfigB>()).Named("FooB"));

现在您有两个不同名称的 IFoo 注册。每个 returns 相同类型的对象 (Foo) 但每个实例都有不同的 IFooConfiguration.

实现

或者,如果您正在使用来自 Settings.Default 的 IFooConfig 实例,您可以这样做:

Component.For<IFoo, Foo>()
    .DependsOn(Dependency.OnValue("config", Default.Settings.FooConfigA))
    .Named("FooA")
    .IsFallback(),
Component.For<IFoo, Foo>()
    .DependsOn(Dependency.OnValue("config", Default.Settings.FooConfigB))
    .Named("FooB"),

现在,对于依赖于 IFoo 的每个 class,您必须通过名称指定您获得的版本,或者您将获得 "A",因为它被指定为回退。

如您所见,这很快就会变得混乱和复杂。如果可能,另一种方法是使用抽象工厂在运行时选择实现,而不是为每个依赖项组合单独注册 IFooHere's some more explanation and an example..

如果您使用的是 Windsor(我相信其他容器也有类似的行为),您可以有一个构造函数,该构造函数采用 IEnumerable<IFoo>Foo[],然后 Windsor 将解析 all 实现并将它们传递给构造函数。您可以将其添加到容器设置中:

container.Kernel.Resolver.AddSubResolver(new ListResolver(container.Kernel, true));

使用 Simple Injector 时,以下注册等同于您当前的 Pure DI 方法:

var container = new Container();

container.Register<ISimpleDependency, SimpleDependency>();
container.Register<ISomeOtherDependency, SomeOtherDependency>();
container.Register<Bar>();
container.Register<Baz>();
container.Register<Qux>();

var fooAReg = Lifestyle.Transient.CreateRegistration<IFoo>(
    () => new Foo(Settings.Default.FooConfigA, container.GetInstance<ISimpleDependency>()),
    container);

var fooBReg = Lifestyle.Transient.CreateRegistration<IFoo>(
    () => new Foo(Settings.Default.FooConfigB, container.GetInstance<ISimpleDependency>()),
    container);

// The registrations for IFoo are conditional. We use fooA for Bar and fooB for Baz.
container.RegisterConditional(typeof(IFoo), fooAReg, 
    c => c.Consumer.ImplementationType == typeof(Bar));
container.RegisterConditional(typeof(IFoo), fooBReg, 
    c => c.Consumer.ImplementationType == typeof(Baz));

container.RegisterCollection<IFoo>(new[] { fooAReg, fooBReg });

请注意,此示例并未对所有注册使用自动装配; Foo 注册是手动连接的。这是因为(作为一项安全措施)Simple Injector 不允许 'looking up' 调用图超过其直接父级,因为这可能导致不正确的结果。这就是为什么我们不能使用自动装配将 FooConfigB 注入 BarFoo

更新:

What if I have two instances of Bar, one of which depends on the Foo with FooConfigA, and the other of which depends on the Foo with FooConfigB

var fooAProd = Lifestyle.Transient.CreateProducer<IFoo>(
    () => new Foo(Settings.Default.FooConfigA, container.GetInstance<ISimpleDependency>()),
    container);

var bar1Reg = Lifestyle.Transient.CreateRegistration<Bar>(() => new Bar(
    fooAProd.GetInstance(),
    container.GetInstance<IOtherDep1>()));

var fooBProd = Lifestyle.Transient.CreateProducer<IFoo>(
    () => new Foo(Settings.Default.FooConfigB, container.GetInstance<ISimpleDependency>()),
    container);

var bar2Reg = Lifestyle.Transient.CreateRegistration<Bar>(() => new Bar(
    fooBProd.GetInstance(),
    container.GetInstance<IOtherDep1>()));

// The registrations for IFoo are conditional. We use fooA for Bar and fooB for Baz.
container.RegisterConditional(typeof(IFoo), fooBProd.Registration, 
    c => c.Consumer.ImplementationType == typeof(Baz));

container.RegisterCollection<IFoo>(new[] { fooAReg, fooBReg });