简体   繁体   中英

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

I have an app which uses dependency injection but does not currently use any IoC container. I currently have something like the following in my app setup code:

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

In order to reduce the complexity of my app setup code and to improve maintainability, I would like to introduce an IoC container. Is there any way I can use an IoC container here to auto-wire everything without tightly coupling the Foo/Bar/Baz/Qux classes to the choice of IoC container implementation?

You'll end up with setup code that is coupled to the IOC container, but that's okay. The key is that the setup always occurs in the composition root of your application. Or in other words, the dependencies are specified at application startup before the classes are used. The classes themselves would operate with no awareness of the container or how the dependencies are being created.

Suppose these are your Foo classes and interfaces:

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 { }

Here's some container code. As you can see it gets complicated. If your dependencies are small and simple it may not be worth it.

Using Windsor as an example, your setup might look something like this. There's more than one way to do it, so I'll leave it to you decide whether it's simpler or preferable.

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"));

Now you have two registrations of IFoo with different names. Each one returns the same type of object ( Foo ) but each of those instances has a different implementation of IFooConfiguration .

Or if you're using instances of IFooConfig that come from Settings.Default , you would do this:

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"),

Now for each class that depends on IFoo you'd have to either specify by name which version you get or you'll just get "A" because that's designated as the fallback.

As you can see, this quickly gets messy and complicated. Another approach, if possible, is to use an abstract factory to choose an implementation at runtime instead of having a separate registration for IFoo for each combination of dependencies. Here's some more explanation and an example. .

If you're using Windsor (and I'm sure other containers have similar behaviors) you could have a constructor that takes an IEnumerable<IFoo> or Foo[] , and then Windsor will resolve all implementations and pass them to the constructor. You would add this to the container setup:

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

When using Simple Injector, the following registration is the equivalent of your current Pure DI approach:

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 });

Do note that this example does not use Auto-Wiring on all registrations; the Foo registrations are wired manually. This is because (as a safety measure) Simple Injector does not allow 'looking up' the call graph past its direct parent, because that could lead to incorrect results. This is why we can't inject FooConfigB into the Foo of Bar using Auto-Wiring.

UPDATE :

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 });

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM