首先,抱歉模糊的问题标题。我无法想出更精确的一个。
鉴于以下类型:
{ 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
答案 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容器。