简体   繁体   中英

Unity: resolve class with two implementations of one interface

I have structure like following:

static void Main(string[] args) {
    var container = new UnityContainer();
    container.RegisterType<IBaseService, ServiceA>("a");
    container.RegisterType<IBaseService, ServiceB>("b");
    container.RegisterType<IWorker, Worker>();

    var runner = container.Resolve<Runner>();
    /* ... */
}

public class Runner {
    public Runner(IWorker worker) {}

}

public class Worker : IWorker {
    public Worker(IBaseService a, IBaseService b) { /* ... */ }
}

public class ServiceA : IBaseService {}
public class ServiceB : IBaseService {}

I know I may set additional interface for ServiceA like IServiceA (the same for ServiceB - IServiceB) and register them in container. But is it possible to resolve instance of Worker with two services having the same contract IBaseService (first param should get ServiceA, second - ServiceB) or it would led to sort of ambiguity?

If you try and run the code as posted you will receive an exception:

The current type, IBaseService, is an interface and cannot be constructed. Are you missing a type mapping?

This is because there is no default registration (default meaning with no name) registered in the container for IBaseService. There are only two named interface mappings registered.

There are few ways to handle this situation. One way, as mentioned by @Haukinger, is to inject all IBaseService implementations into the object.

So you could declare your class like this:

public class Worker2 : IWorker
{
    public Worker2(IEnumerable<IBaseService> baseServices)
    {
        /* ... */
    }
}

and then register with the container like this:

container.RegisterType(typeof(IEnumerable<IBaseService>), typeof(IBaseService[]));

This maps an IEnumerable<IBaseService> to an array of IBaseService which works because when Unity sees an array it will inject use all named registrations and inject those into the object. You could also use an array directly without the IEnumerable mapping.

This approach can be useful but more often than not you want to know which type is being injected (eg a factory where the correct implementation must be chosen at runtime based on some other criteria).

Another approach is to configure Unity to inject the correct named registrations into the Worker:

container.RegisterType<IWorker, Worker>(
    new InjectionConstructor(
        new ResolvedParameter<IBaseService>("a"), 
        new ResolvedParameter<IBaseService>("b")));

In the above case, we tell Unity what constructor to use and which implementation to inject.

Another approach is to use an InjectionFactory:

container.RegisterType<IWorker, Worker>(
    new InjectionFactory(
        c => new Worker(c.Resolve<IBaseService>("a"), c.Resolve<IBaseService>("b"))
        ));

One benefit of using the InjectionFactory is that because we are newing-up the Worker object we get compiler checking of the constructor. eg if the Worker constructor was changed to add a new parameter then there would be a compilation error. In constrast, the InjectionConstructor approach would compile but would generate a runtime error System.InvalidOperationException: The type Worker does not have a constructor that takes the parameters (IBaseService, IBaseService).

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