自定义 Autofac 的组件解析/通用协方差/逆变问题

发布于 2024-11-29 08:51:37 字数 1961 浏览 1 评论 0原文

首先,对模糊的问题标题表示歉意。我无法想出一个更精确的答案。

给定这些类型:

                                                     { 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 ,即。通过上面示例中的 FooCommandHandler

Autofac 是否可以为此目的进行定制,也许可以使用定制注册源?


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

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

First, sorry for the vague question title. I couldn't come up with a more precise one.

Given these types:

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

I would like to write a method Dispatch that accepts any command and sends it to an appropriate ICommandHandler<>. I thought that using a DI container (Autofac) might greatly simplify the mapping from a command's type to a command handler:

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

Let's say the DI container knows about all the types shown above. Now I'm calling:

Dispatch(new SpecialFooCommand(…));

In reality, this will result in Autofac throwing a ComponentNotRegisteredException, since there is no ICommandHandler<SpecialFooCommand> available.

Ideally however, I would still want a SpecialFooCommand to be handled by the closest-matching command handler available, ie. by a FooCommandHandler in the above example.

Can Autofac be customized towards that end, perhaps with a custom registration source?


P.S.: I understand that there might be the fundamental problem of co-/contravariance getting in the way (as in the following example), and that the only solution might be one that doesn't use generics at all... but I would want to stick to generic types, if possible.

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

心头的小情儿 2024-12-06 08:51:37

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

根据 Daniel 的回答,您需要添加 in 修饰符到 ICommandHandlerTCommand 参数:

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 软件包,请从 http://code.google.com/p/autofac/source/browse/src/Source/Autofac/Features/Variance/ContravariantRegistrationSource.cs - 它应该针对任何最新的 Autofac 版本进行编译。

Not really a fair answer, as I've extended Autofac since you posted the question... :)

As per Daniel's answer, you'll need to add the in modifier to the TCommand parameter of ICommandHandler:

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

Autofac 2.5.2 now includes an IRegistrationSource to enable contravariant Resolve() operations:

using Autofac.Features.Variance;

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

With this source registered, services represented by a generic interface with a single in parameter will be looked up taking variant implementations into account:

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

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

Both calls to Resolve() will successfully retrieve the FooCommandHandler.

If you can't upgrade to the latest Autofac package, grab the ContravariantRegistrationSource from http://code.google.com/p/autofac/source/browse/src/Source/Autofac/Features/Variance/ContravariantRegistrationSource.cs - it should compile against any recent Autofac build.

弥枳 2024-12-06 08:51:37

如果没有自己的编码,您所问的问题是不可能的。
基本上,您要问以下问题:如果找不到我尝试解析的类型,请返回可以转换为该类型的另一种类型,例如,如果您尝试解析 IEnumerable 返回已注册的类型对于ICollection
不支持此操作。
一种简单的解决方案如下:
FooCommandHandler 注册为 ICommandHandler 的处理程序。为此,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);

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

What you are asking is not possible without own coding.
Basically, you are asking the following: If the type I tried to resolve isn't found, return another type that can be converted to it, e.g. if you try to resolve IEnumerable return a type that is registered for ICollection.
This is not supported.
One simple solution would be the following:
Register FooCommandHandler as a handler for ICommandHandler<SpecialFooCommand>. For this to work, ICommandHandler needs to be 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: The way you are using the container, you apply the Service locator anti-pattern. This should be avoided.

人生戏 2024-12-06 08:51:37

我喜欢添加一种替代方法,该方法在没有 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 容器。

I like to add an alternative approach, which also works without C# 4.0 variance support.

You can create a special decorator / wrapper that allows executing a command as its base type:

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

With this in place, the following line of code would allow you to handle SpecialFooCommand as its base type:

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

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

Note that the use of such VarianceHandler works for most DI containers.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文