自定义Autofac的组件分辨率/具有通用协同/逆变的问题

sta*_*ica 18 c# customization resolution autofac generic-variance

首先,抱歉模糊的问题标题.我无法想出更精确的一个.

鉴于以下类型:

                                                     { TCommand : ICommand }
       «interface»                   «interface»    /
      +-----------+         +----------------------/----+
      | ICommand  |         | ICommandHandler<TCommand> |
      +-----------+         +---------------------------+
            ^               | Handle(command: TCommand) |
            |               +---------------------------+
            |                              ^
            |                              |
      +------------+            +-------------------+
      | FooCommand |            | FooCommandHandler |
      +------------+            +-------------------+
            ^
            |
   +-------------------+
   | SpecialFooCommand |
   +-------------------+
Run Code Online (Sandbox Code Playgroud)

我想编写一个Dispatch接受任何命令并将其发送到适当的方法ICommandHandler<>.我认为使用DI容器(Autofac)可能会大大简化从命令类型到命令处理程序的映射:

void Dispatch<TCommand>(TCommand command) where TCommand : ICommand
{
    var handler = autofacContainer.Resolve<ICommandHandler<TCommand>>();
    handler.Handle(command);
}
Run Code Online (Sandbox Code Playgroud)

假设DI容器知道上面显示的所有类型.现在我打电话给:

Dispatch(new SpecialFooCommand(…));
Run Code Online (Sandbox Code Playgroud)

实际上,这将导致Autofac抛出一个ComponentNotRegisteredException,因为没有ICommandHandler<SpecialFooCommand>可用的.

然而,理想情况下,我仍然希望SpecialFooCommand由最接近匹配的命令处理程序处理,即.通过FooCommandHandler在上面的例子.

可以通过自定义注册源为此自定义Autofac吗?


PS:我理解可能存在共同/逆变的基本问题(如下例所示),并且唯一的解决方案可能是根本不使用泛型的解决方案......但我会如果可能的话,我想坚持使用泛型类型.

ICommandHandler<FooCommand> fooHandler = new FooCommandHandler(…);
ICommandHandler<ICommand> handler = fooHandler;
//                                ^
//              doesn't work, types are incompatible
Run Code Online (Sandbox Code Playgroud)

Nic*_*rdt 17

这不是一个公平的答案,因为我发布了问题后我已经扩展了Autofac ... :)

根据Daniel的回答,您需要将in修饰符添加到以下TCommand参数中ICommandHandler:

interface ICommandHandler<in TCommand>
{
    void Handle(TCommand command);
}
Run Code Online (Sandbox Code Playgroud)

Autofac 2.5.2现在包含一个IRegistrationSource启用逆变Resolve()操作:

using Autofac.Features.Variance;

var builder = new ContainerBuilder();
builder.RegisterSource(new ContravariantRegistrationSource());
Run Code Online (Sandbox Code Playgroud)

注册此源后,in将查找具有单个参数的通用接口所代表的服务,并将变体实现考虑在内:

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

var container = builder.Build();
container.Resolve<ICommandHandler<FooCommand>>();
container.Resolve<ICommandHandler<SpecialFooCommand>>();
Run Code Online (Sandbox Code Playgroud)

两次调用Resolve()都会成功检索到FooCommandHandler.

如果您无法升级到最新的Autofac软件包,请ContravariantRegistrationSourcehttp://code.google.com/p/autofac/source/browse/src/Source/Autofac/Features/Variance/ContravariantRegistrationSource.cs中获取- 它应该编译对任何最近的Autofac构建.


Dan*_*rth 5

没有自己的编码,你所要求的是不可能的.基本上,您要求以下内容:如果找不到我尝试解析的类型,请返回另一种可以转换为它的类型,例如,如果您尝试解析IEnumerable返回已注册的类型ICollection.这不受支持.一个简单的解决方案如下:注册FooCommandHandler为处理程序ICommandHandler<SpecialFooCommand>.为此,ICommandHandler需要逆变:

interface ICommand { }

class FooCommand : ICommand { }

class SpecialFooCommand : FooCommand { }

interface ICommandHandler<in T> where T : ICommand
{
    void Handle(T command);
}

class FooCommandHandler : ICommandHandler<FooCommand>
{
    public void Handle(FooCommand command)
    {
        // ...
    }
}

var builder = new ContainerBuilder();
builder.RegisterType<FooCommandHandler>()
       .As<ICommandHandler<SpecialFooCommand>>()
       .As<ICommandHandler<FooCommand>>();
var container = builder.Build();
var fooCommand = new FooCommand();
var specialCommand = new SpecialFooCommand();
container.Resolve<ICommandHandler<FooCommand>>().Handle(fooCommand);
container.Resolve<ICommandHandler<FooCommand>>().Handle(specialCommand);
container.Resolve<ICommandHandler<SpecialFooCommand>>().Handle(specialCommand);
Run Code Online (Sandbox Code Playgroud)

顺便说一句:您使用容器的方式,您应用服务定位器反模式.这应该避免.