18

首先,对于模糊的问题标题感到抱歉。我想不出更精确的。

鉴于这些类型:

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

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

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

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

Dispatch(new SpecialFooCommand(…));

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

然而,理想情况下,我仍然希望 aSpecialFooCommand由可用的最匹配的命令处理程序处理,即。上面例子中的 a FooCommandHandler

是否可以为此定制 Autofac,也许使用自定义注册源?


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

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

3 回答 3

17

不是一个真正公平的答案,因为自从您发布问题以来我已经扩展了 Autofac... :)

根据丹尼尔的回答,您需要将in修饰符添加到TCommand参数中ICommandHandler

interface ICommandHandler<in TCommand>
{
    void Handle(TCommand command);
}

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

using Autofac.Features.Variance;

var builder = new ContainerBuilder();
builder.RegisterSource(new ContravariantRegistrationSource());

in注册此源后,将在考虑变体实现的情况下查找由具有单个参数的通用接口表示的服务:

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

var container = builder.Build();
container.Resolve<ICommandHandler<FooCommand>>();
container.Resolve<ICommandHandler<SpecialFooCommand>>();

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

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

于 2011-08-12T10:16:18.320 回答
5

如果没有自己的编码,您所问的内容是不可能的。基本上,您要问以下问题:如果找不到我尝试解析的类型,请返回另一种可以转换为它的类型,例如,如果您尝试解析IEnumerable返回已注册的类型ICollection。这是不支持的。一个简单的解决方案如下:注册FooCommandHandlerICommandHandler<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);

顺便说一句:您使用容器的方式是应用服务定位器反模式。应该避免这种情况。

于 2011-08-10T13:07:53.680 回答
3

我喜欢添加一种替代方法,它也可以在没有 C# 4.0 差异支持的情况下工作。

您可以创建一个特殊的装饰器/包装器,允许将命令作为其基本类型执行:

public class VarianceHandler<TSubCommand, TBaseCommand> 
    : ICommandHandler<TSubCommand>
    where TSubCommand : TBaseCommand
{
    private readonly ICommandHandler<TBaseCommand> handler;

    public VarianceHandler(ICommandHandler<TBaseCommand> handler)
    {
        this.handler = handler;
    }

    public void Handle(TSubCommand command)
    {
        this.handler.Handle(command);
    }
}

有了这个,以下代码行将允许您将SpecialFooCommand其作为其基本类型进行处理:

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

builder.Register<VarianceHandler<SpecialFooCommand, FooCommand>>()
    .As<ICommandHandler<SpecialFooCommand>>();

请注意,VarianceHandler对大多数 DI 容器使用此类作品。

于 2011-08-14T12:47:14.700 回答