【问题标题】:Open Generics and IEnumerable with Ninject使用 Ninject 开放泛型和 IEnumerable
【发布时间】:2023-03-31 08:03:01
【问题描述】:

我有如下界面...

public interface IHandler<in TFor> where TFor : IRequest
{
    void Handle(IEnumerable<TFor> requests);
}

通常是这样实现的...

public class AssignmentHandler : HandlerBase, IHandler<AssignmentRequest>
{
    public void Handle(IEnumerable<AssignmentRequest> assigmentRequests)
    {
        foreach(var request in assignmentRequests).....
    }
}

IRequest 只是一个标记接口(此时)。

我通过以下约定注册所有处理程序...

public override void Load()
    {

        Kernel.Bind(x => x.FromAssemblyContaining<IHandler>()
            .SelectAllClasses()
            .InheritedFrom(typeof(IHandler<>))
            .BindAllInterfaces());

        //i also tried...
        Kernel.Bind(x => x.FromAssemblyContaining<IHandler>()
            .SelectAllClasses()
            .InheritedFrom<IHandler<IRequest>>()
            .BindSingleInterface());

    }

他们单独解决就好了。

在一种情况下,我想解析所有处理程序,并将它们注入到这样的构造函数中......

public SomeConstructor(IEnumerable<IHandler<IRequest>> allHandlers)

这不起作用,总是返回空。

我的理解是因为我按照惯例将它们注册为IHandler&lt;ConcreteRequest&gt;,而不是IHandler&lt;IRequest&gt;,这是两个不同的签名。

我如何按照惯例注册所有处理程序,以将它们共同标识为IEnumerable&lt;IHandler&lt;IRequest&gt;&gt;,同时也单独标识?

第二次注册是可以的,但更希望通过两个签名解决一个实现。

【问题讨论】:

  • 无法将处理程序 FooHandler : IHandler&lt;FooRequest&gt; 强制转换为 IHandler&lt;IRequest&gt;。使用任何 IoC 都无法实现这一点,因为 TRequest 类型参数不能同时为 inout..
  • 可以直接使用IRequest接口,为什么还要使用TFor泛型?
  • 因为每个请求类型都有自己的处理程序类型。

标签: c# ninject open-generics


【解决方案1】:

不是您问题的答案,但在我看来,您缺少抽象,因为您的所有处理程序都包含相同的 foreach 循环,这意味着您违反了 DRY。我建议将界面更改为以下内容:

public interface IHandler<in TFor> where TFor : IRequest
{
    void Handle(TFor request);
}

并且有一个允许处理多个实例的通用实现:

public class CompositeRequest<TFor> : IRequest
{
    public CompositeRequest(params TFor[] requests)
    {
        this.Requests = requests;
    }

    public TFor[] Requests { get; private set; }
}

public class CompositeHandler<TFor> : IHandler<CompositeRequest<TFor>> 
    where TFor : IRequest
{
    private readonly IHandler<TFor> handler;

    public CompositeHandler(IHandler<TFor> handler)
    {
        this.handler = handler;
    }

    public void Handle(CompositeRequest<TFor> request)
    {
        foreach (var r in request.Requests)
        {
            this.handler.Handle(r);
        }
    }
}

这消除了每个处理程序实现 foreach 循环的需要,如果处理列表的方式发生变化,您只需在一个地方进行更改。

如何在 Ninject 中注册这个,可惜我不知道。

更新

使用 Simple Injector,注册过程如下:

// using SimpleInjector.Extensions;

// Batch register all handlers in the system.
container.RegisterManyForOpenGeneric(typeof(IHandler<>), 
    typeof(AssignmentHandler).Assembly);

// Register the open-generic CompositeHandler<TFor>
container.RegisterOpenGeneric(typeof(IHandler<>), typeof(CompositeHandler<>));

【讨论】:

  • Steven,你将如何在 SimpleInjector 中注册它?它可能会提供一个令人信服的理由来切换:)
  • @Baldy:在回答其他 DI 容器的问题时,我尽量避免推广 Simple Injector,但既然您明确提出:请查看我的更新 :-)。
  • 对于 ninject,打开的通用绑定是这样完成的:IBindingRoot.Bind(typeof(IHandler&lt;&gt;)).To(typeof(MultipleHandler&lt;&gt;));
  • @BatteryBackupUnit:你试过吗?那是行不通的。当您尝试解析复合处理程序时,Ninject 会抛出“检测到循环依赖”异常。
  • 好吧,对不起,是的,我忽略了那部分。我已经更新了我的答案,以反映如何使用 ninject 来实现。 Ninject 确实不支持像.Bind(typeof(IHandler&lt;SomeOpenGenericType&lt;&gt;&gt;)) 这样的符号。首先,编译器不支持它。其次,您可以通过 typeof(CompositeHandler).GetInterfaces().Single() 指定它。但是 ninject 不支持这一点(而且 - 这不是很好)。所以你得引入一个中间接口ICompositeHandler : IHandler&lt;CompositeRequest&lt;T&gt;&gt; where T : IRequest
【解决方案2】:

处理程序 FooHandler : IHandler&lt;FooRequest&gt; 不能转换为 IHandler&lt;IRequest&gt;。使用任何 IoC 都无法实现这一点,因为 TRequest 类型参数不能同时输入和输出。

如果您想要“双向”,则必须将处理程序绑定到某个通用接口,例如空的IHandler 接口。然后注入所有这些并通过反射找到正确的并使用转换为正确类型的参数调用它。

当然,另一种选择是将void Handle(IEnumerable&lt;TFor&gt; requests) 的签名更改为void Handle(IEnumerable&lt;IRequest&gt; requests),并让实现进行转换。但我认为这对您的场景来说是一个更糟糕的解决方案。

对于每种类型,您还需要两个不同的绑定或一个多重绑定,例如 Bind&lt;IHandler&lt;Foo&gt;, IHandler&gt;().To&lt;FooHandler&gt;().InSingletonScope()。 当您需要范围界定时,多重绑定非常有用。如果您有两个使用InSingletonScope 的相同类型的绑定,则将有两个实例。如果使用多重绑定,则只有一个实例。


要使 StevenCompositeHandler 与 ninject 一起使用,您必须稍微调整解决方案并为复合处理程序引入一个新接口:

public class CompositeRequest<TFor> : IRequest
{
    public CompositeRequest(params TFor[] requests)
    {
        this.Requests = requests;
    }

    public TFor[] Requests { get; private set; }
}

public interface ICompositeHandler<TFor> : IHandler<CompositeRequest<TFor>> { }

public class CompositeHandler<TFor> : ICompositeHandler<TFor>
    where TFor : IRequest
{
    private readonly IHandler<TFor> handler;

    public CompositeHandler(IHandler<TFor> handler)
    {
        this.handler = handler;
    }

    public void Handle(CompositeRequest<TFor> request)
    {
        foreach (var r in request.Requests)
        {
            this.handler.Handle(r);
        }
    }
}

然后按如下方式创建绑定:

        var kernel = new StandardKernel();

        kernel.Bind(typeof(ICompositeHandler<>)).To(typeof(CompositeHandler<>));

        kernel.Bind(x => x.FromThisAssembly()
            .SelectAllClasses()
            .InheritedFrom(typeof(IHandler<>))
            .Excluding(typeof(CompositeHandler<>))
            .BindDefaultInterfaces());

        kernel.Get<ICompositeHandler<Foo>>();

我已经验证它可以工作。

【讨论】:

  • 是的,按照建议更改签名涉及到选角,感觉很乱。尽管如此,还是对情况进行了很好的描述:)
猜你喜欢
  • 1970-01-01
  • 2015-10-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-10-08
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多