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

时间:2011-08-10 11:43:32

标签: c# customization resolution autofac generic-variance

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

鉴于以下类型:

                                                     { 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抛出ComponentNotRegisteredException,因为没有ICommandHandler<SpecialFooCommand>可用。

然而,理想情况下,我仍然希望由最接近匹配的命令处理程序处理SpecialFooCommand,即。在上面的示例中使用FooCommandHandler

可以使用自定义注册源自定义Autofac吗?


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

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

3 个答案:

答案 0 :(得分:17)

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

根据Daniel的回答,您需要将in修饰符添加到TCommand的{​​{1}}参数中:

ICommandHandler

Autofac 2.5.2现在包含interface ICommandHandler<in TCommand> { void Handle(TCommand command); } 以启用逆向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()

如果您无法升级到最新的Autofac软件包,请从http://code.google.com/p/autofac/source/browse/src/Source/Autofac/Features/Variance/ContravariantRegistrationSource.cs抓取FooCommandHandler - 它应针对最近的任何Autofac版本进行编译。

答案 1 :(得分:5)

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

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

BTW:您使用容器的方式是Service locator anti-pattern。这应该避免。

答案 2 :(得分: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容器。