简体   繁体   中英

Resolving Autofac enumerable dependencies without contravariance

My application has two command classes FooCommand and BarCommand , where BarCommand is a subclass of FooCommand .

class FooCommand
class BarCommand : FooCommand

I then have classes to execute these commands.

class FooCommandHandler : ICommandHandler<FooCommand>
class BarCommandHandler : ICommandHandler<BarCommand>

These command handlers are registered with Autofac as ICommandHandler<> services.

builder.RegisterType<FooCommandHandler>.As<ICommandHandler<FooCommand>>();
builder.RegisterType<BarCommandHandler>.As<ICommandHandler<BarCommand>>();

Then when I need to execute a command, I resolve the registered handlers using Autofac's enumeration relationship type , which works fine.

var barCommandHandlers = container.Resolve<IEnumerable<ICommandHandler<FooCommand>>>()
// returns [ FooCommandHandler ]

So far, so good. But when I resolve registered handlers for BarCommand , Autofac matches both the BarCommandHandler and FooCommandHandler implementations because BarCommand derives from FooCommand .

var barCommandHandlers = container.Resolve<IEnumerable<ICommandHandler<BarCommand>>>()
// returns [ BarCommandHandler, FooCommandHandler ]

This behaviour isn't too unexpected, but it's not quite what I want.

Is there a way to resolve IEnumerable<ICommandHandler<BarCommand>> to only provide handlers that directly implement the ICommandHandler<BarCommand> interface, without also including those that implement ICommandHandler<Base> ?

Too long for a comment: I can't duplicate the behavior. Here are some types and a unit test:

public interface ICommandHandler<T> where T : FooCommand { }
public class FooCommand { }
public class BarCommand : FooCommand { }
class FooCommandHandler : ICommandHandler<FooCommand> { }
class BarCommandHandler : ICommandHandler<BarCommand> { }
class BarCommandHandler2 : ICommandHandler<BarCommand> { }

[TestClass]
public class AutofacTests
{
    [TestMethod]
    public void ContainerResolvesExpectedDependency()
    {
        var container = GetContainer();
        var barCommandHandlers = container.Resolve<IEnumerable<ICommandHandler<BarCommand>>>()
            .ToArray();
        Assert.AreEqual(2, barCommandHandlers.Length);
        Assert.IsTrue(barCommandHandlers.Any(bch => bch is BarCommandHandler));
        Assert.IsTrue(barCommandHandlers.Any(bch => bch is BarCommandHandler2));
        Assert.IsFalse(barCommandHandlers.Any(bch => bch is FooCommandHandler));
    }

    private IContainer GetContainer()
    {
        var builder = new ContainerBuilder();
        builder.RegisterType<FooCommandHandler>().As<ICommandHandler<FooCommand>>();
        builder.RegisterType<BarCommandHandler>().As<ICommandHandler<BarCommand>>();
        builder.RegisterType<BarCommandHandler2>().As<ICommandHandler<BarCommand>>();
        return builder.Build();
    }
}

I'm registering the various implementations of ICommandHandler<T> . I included an extra one - BarCommandHandler2 - so I could verify that I'm getting a collection of multiple implementations from the container.

The tests pass. I'm registering all three types, but when I resolve IEnumerable<ICommandHandler<BarCommand>> I only get the two implementations that I expect.

That makes sense because FooCommandHandler doesn't implement ICommandHandler<BarCommand> . If the container did resolve that then it would be a bug.

My recommendation would be to look over the part where you're resolving IEnumerable<ICommandHandler<BarCommand>> . Perhaps you've got some additional generics at work, and at runtime you're actually resolving something else.

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