自定义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修饰符添加到ICommandHandlerTCommand参数:

 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容器。