【问题标题】:Ninject bindings for a dispatcher implementation of an interface接口的调度程序实现的 Ninject 绑定
【发布时间】:2023-11-27 20:22:01
【问题描述】:

我有一个界面:

public interface IService
{
    void DoStuff(int parm1, string parm2, Guid gimmeABreakItsAnExampleK);
}

我想配置 Ninject (v3) 绑定,以便我可以有一个“调度程序”随机播放方法调用IService 的多个实例,如下所示:

public sealed class DispatcherService : IService
{
    private IEnumerable<IService> _children;

    public DispatcherService(IEnumerable<IService> children)
    {
        this._children = children.ToList();
    }

    public void DoStuff(int parm1, string parm2, Guid gimmeABreakItsAnExampleK)
    {
        foreach(var child in this._children)
        {
            child.DoStuff(parm1, parm2, gimmeABreakItsAnExampleK);
        }
    }
}

但是,我的绑定,看起来像这样,最终会在运行时抛出异常,指示循环依赖:

this.Bind<IService>().To<DispatcherService>();

this.Bind<IService>().To<SomeOtherService>()
    .WhenInjectedExactlyInto<DispatcherService>();
this.Bind<IService>().To<YetAnotherService>()
    .WhenInjectedExactlyInto<DispatcherService>();

这可能吗?如果是这样,我做错了什么?忍者能否逃脱这种循环依赖的厄运?

【问题讨论】:

  • 为什么需要WhenInjectedExactlyInto,为什么不只需要To
  • 因为WhenInjectedExactlyInto 防止SomeOtherServiceYetAnotherService 被注入除DispatcherService 之外的任何东西。我使用了类似的方法来“包装”(参见*.com/questions/6752674/…),但我正试图让它适用于多注入情况。
  • 您是否查看过各种 Event Broker 扩展 - 它们都做了您的示例所做的深入研究。好吧,你说这只是一个例子,我会安静的!

标签: c# .net dependency-injection ninject ioc-container


【解决方案1】:

如果您的调度程序是唯一 IService,它将以 IService 列表作为参数,这确实有效(我测试过):

kernel.Bind<IService>().To<DispatcherService>().When(x => x.IsUnique);
this.Bind<IService>().To<SomeOtherService>()
    .WhenInjectedExactlyInto<DispatcherService>();
this.Bind<IService>().To<YetAnotherService>()
    .WhenInjectedExactlyInto<DispatcherService>();

When 子句适用于这种情况的原因是,当您的构造函数调用单个服务实例时,IRequestIsUnique 字段被设置为 true。由于您的DispatcherService 调用IEnumerable,因此激活DispatcherService 时的值为false。这可以防止循环依赖的发生。

确实,告诉内核不要尝试将 DispatcherService 注入自身的任何正确方法都可以工作(这只是一个潜在有用的示例)。

编辑:简单地短路循环依赖的更明确的方法似乎是这样的:

kernel.Bind<IService>().To<DispatcherService>().When(
   request => request.Target.Member.DeclaringType != typeof (DispatcherService));

【讨论】:

  • 谢谢你,好先生!如果您可以更新您的答案以包含有关IRequestIsUnique 标志的一些信息,那么答案和赏金将归您所有。
  • @FMM 谢谢。我希望我的解释不会太笨拙:)
  • 太好了,谢谢!从名称IsUnique 恕我直言,单实例与多实例的区别并不明显。
  • 我同意 request.IsUnique 读起来好像它应该暗示“如果请求应该返回唯一结果则为真”以外的其他内容:)
【解决方案2】:

为什么不把DispatcherService中的IService去掉,取名为IDispatcherService,让被调用的服务(接收者)实现IService呢?

【讨论】:

    【解决方案3】:

    您可以将两个子集(调度程序或接收者)隔离开来,方法是使其中一个成为命名绑定,然后使用名称作为将一个提供给另一个的方式(通过NamedAttribute或在您的接线中)

    【讨论】:

    • 代码示例?听起来DispatcherService 构造函数也需要特定于 Ninject 的属性,我想避免这种情况。
    • 抱歉,这会儿太忙了 - 如果没有示例无法对其进行排序,请再次 ping。在 wiki 上查看命名绑定示例...
    【解决方案4】:

    我必须承认我对 Ninject API 不是很熟悉,但我认为这样可以解决问题:

    kernel.Bind<IService>().To<DispatcherService>();   
    
    kernel.Bind<IEnumerable<IService>>().ToMethod(() => new IService[]
    {
        kernel.Get<SomeOtherService>(),
        kernel.Get<YetAnotherService>(),
    });
    

    【讨论】:

    • 不起作用。仍然会导致周期性依赖。使用IService[] 而不是IEnumerable&lt;IService&gt; 也没有影响。
    • 对不起。我想我明白为什么(但不确定如何在 Ninject 中解决这个问题)。 Ninject 允许使用不同的实现多次调用Bind&lt;IService&gt;(),Ninject 将使用它们来解析集合。 (这是大多数容器都有的功能,我不太喜欢)。我认为这会覆盖您的手动IEnumerable&lt;IService&gt; 注册。我认为诀窍是注册一个IList&lt;IService&gt;IService[] 并让你的DispatcherService 直接依赖于这个IList&lt;IService&gt;IService[]