自定义Autofac的组件分辨率/具有通用协同/反演的问题
首先,抱歉模糊的问题标题。 我无法想出更准确的一个。
鉴于以下类型:
{ TCommand : ICommand } «interface» «interface» / +-----------+ +----------------------/----+ | ICommand | | ICommandHandler | +-----------+ +---------------------------+ ^ | Handle(command: TCommand) | | +---------------------------+ | ^ | | +------------+ +-------------------+ | FooCommand | | FooCommandHandler | +------------+ +-------------------+ ^ | +-------------------+ | SpecialFooCommand | +-------------------+
我想写一个方法Dispatch
接受任何命令并将其发送到适当的ICommandHandler
。 我认为使用DI容器(Autofac)可能会大大简化从命令类型到命令处理程序的映射:
void Dispatch(TCommand command) where TCommand : ICommand { var handler = autofacContainer.Resolve<ICommandHandler>(); handler.Handle(command); }
假设DI容器知道上面显示的所有类型。 现在我打电话给:
Dispatch(new SpecialFooCommand(…));
实际上,这将导致Autofac抛出ComponentNotRegisteredException
,因为没有ICommandHandler
可用。
然而,理想情况下,我仍然希望SpecialFooCommand
由最接近匹配的命令处理程序处理,即。 在上面的例子中由FooCommandHandler
提供。
可以通过自定义注册源为此自定义Autofac吗?
PS:我理解可能存在共同/逆变的基本问题(如下例所示),并且唯一的解决方案可能是根本不使用generics的解决方案……但我会如果可能的话,我想坚持使用generics类型。
ICommandHandler fooHandler = new FooCommandHandler(…); ICommandHandler handler = fooHandler; // ^ // doesn't work, types are incompatible
这不是一个公平的答案,因为我已经发布了问题,因为我已经扩展了Autofac … 🙂
根据Daniel的回答,您需要将in
修饰符添加到ICommandHandler
的TCommand
参数:
interface ICommandHandler { 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() .As>(); var container = builder.Build(); container.Resolve>(); container.Resolve>();
两次调用Resolve()
都会成功检索FooCommandHandler
。
如果您无法升级到最新的Autofac软件包, ContravariantRegistrationSource
从http://code.google.com/p/autofac/source/browse/src/Source/Autofac/Features/Variance/ContravariantRegistrationSource.cs获取ContravariantRegistrationSource
– 它应该针对任何最近的Autofac构建进行编译。
没有自己的编码,你所要求的是不可能的。 基本上,您要求以下内容:如果找不到我尝试解析的类型,则返回另一种可以转换为它的类型,例如,如果您尝试解析IEnumerable
返回为ICollection
注册的类型。 这不受支持。 一个简单的解决方案如下:将FooCommandHandler
注册为ICommandHandler
的处理程序。 为此,ICommandHandler需要逆变 :
interface ICommand { } class FooCommand : ICommand { } class SpecialFooCommand : FooCommand { } interface ICommandHandler where T : ICommand { void Handle(T command); } class FooCommandHandler : ICommandHandler { public void Handle(FooCommand command) { // ... } } var builder = new ContainerBuilder(); builder.RegisterType() .As>() .As>(); var container = builder.Build(); var fooCommand = new FooCommand(); var specialCommand = new SpecialFooCommand(); container.Resolve>().Handle(fooCommand); container.Resolve>().Handle(specialCommand); container.Resolve>().Handle(specialCommand);
顺便说一句:您使用容器的方式,您应用服务定位器反模式 。 这应该避免。
我想添加一种替代方法,它也可以在没有C#4.0方差支持的情况下工作。
您可以创建一个特殊的装饰器/包装器,允许执行命令作为其基本类型:
public class VarianceHandler : ICommandHandler where TSubCommand : TBaseCommand { private readonly ICommandHandler handler; public VarianceHandler(ICommandHandler handler) { this.handler = handler; } public void Handle(TSubCommand command) { this.handler.Handle(command); } }
有了这个,下面的代码行将允许您处理SpecialFooCommand
作为其基本类型:
builder.Register() .As>(); builder.Register>() .As>();
请注意,使用此类VarianceHandler
适用于大多数DI容器。