【问题标题】:Stack overflow when resolving services with Ninject使用 Ninject 解析服务时堆栈溢出
【发布时间】:2013-03-04 12:32:38
【问题描述】:

我正在尝试使用 NInject 解析已知的 Foo 服务固定列表(在单例范围内);此解决方案发生在 FooProvider 的构造函数中。问题是每个 Foo 也需要这个提供者。

public interface IFoo { }
public interface IFooProvider { }

public class Foo : IFoo
{
    private readonly IFooProvider _provider;

    public Foo(IFooProvider provider)
    {
        _provider = provider;
    }
}

public class FooProvider : IFooProvider
{
    private List<IFoo> _allFooServices;

    public FooProvider(IKernel kernel)
    {
        _allFooServices = kernel.GetAll<IFoo>().ToList();
    }
}

public class Program
{
    private static void Main(string[] args)
    {
        var IoC = new StandardKernel();

        IoC.Bind<IFoo>().To<Foo>().InSingletonScope();
        IoC.Bind<IFooProvider>().To<FooProvider>().InSingletonScope();

        var foo = IoC.Get<IFoo>();
    }
}

这里有一个逻辑循环,显然堆栈溢出表明它正在下降。但是,我将两个接口都绑定到单例。

考虑一下;我们尝试解析 IFoo,然后需要解析 IFooProvider,它本身需要一个 IFoo 列表……但我们还没有解析任何 IFoo 单例,因为我们仍在尝试解决它!

那么我该如何解决这个问题?

[编辑] 可能的解决方案; IFoo 服务实例的延迟缓冲。

public FooProvider(IKernel kernel)
{
    _kernel = kernel;
}

public IFoo Find(object context)
{
    if (_allFooServices == null)
        _allFooServices = _kernel.GetAll<IFoo>().ToList();

    return _allFooServices.Where(...

[为什么?]

一般的想法是避免服务定位器模式,因为我已经看到它被描述为一种反模式。因此,与其在运行时尝试通过依赖注入器解析服务;您尝试在设置过程中获取服务列表。但是,这样做的问题是,如果您的任何服务想要找到其他服务,您就会遇到上述问题。

【问题讨论】:

  • 那么为什么Foo 需要FooProvider 的实例?如果事实上FooProvider 是一个创建Foo 的工厂,那么它应该可以通过其他方式轻松获得是吗?
  • 所有 Foo 都是服务,FooProvider 试图在注入时获取所有 Foo 服务的(已解析)列表。如果任何 foo 需要在运行时找到另一个 foo(特定于上下文),它可以在运行时询问 FooProvider。
  • 那么FooProvider 不应该是一个可以通过所有其他Foo 的依赖注入轻松解决的单例吗?
  • 它们被绑定为单例。
  • 那么如果FooProvider 已经被绑定为一个单例,如果你需要一个作为Foo 只需使用依赖注入来恢复它。真的应该这么简单。

标签: c# dependency-injection ninject stack-overflow


【解决方案1】:

您无法使用 Ninject 解决这种循环依赖。您甚至无法手动创建这样的对象图。

首先你必须从至少一个构造函数中移除循环依赖。您可以将此依赖项移至属性并使用属性注入。

public class Foo : IFoo
{
   [Inject]
   public IFooProvider Provider { get; set; }
}

如果您想避免服务定位器模式,您应该从FooProvider 的构造函数中移除对IKernel 的依赖,并改用注入已注册IFoo 实现的集合。

public class FooProvider : IFooProvider
{
    private List<IFoo> _allFooServices;

    public FooProvider(IEnumerable<IFoo> fooServices)
    {
        _allFooServices = fooServices.ToList();
    }
}

【讨论】:

    【解决方案2】:

    您可以对其中之一使用属性注入。

    public interface IFoo
    {
    }
    
    public interface IFooProvider
    {
    }
    
    public class Foo : IFoo
    {
       [Inject]
       public IFooProvider Provider { get; set; }
    }
    
    public class FooProvider : IFooProvider
    {
       private List<IFoo> _allFooServices;
    
       public FooProvider(IKernel kernel)
       {
          _allFooServices = kernel.GetAll<IFoo>().ToList();
       }
    }
    
    private static void Main(string[] args)
    {
       var IoC = new StandardKernel();
       IoC.Bind<IFoo>().To<Foo>().InSingletonScope();
       IoC.Bind<IFooProvider>().To<FooProvider>().InSingletonScope();
    
       var foo = IoC.Get<IFoo>();
     }
    

    【讨论】:

      【解决方案3】:

      确实这似乎是糟糕的设计,但要直接回答您的问题,请不要在构造函数中实例化 _allFooServices。你可以这样做:

      private List<IFoo> _allFooServices;
      private List<IFoo> AllFooServices
      {
          get { return _allFooServices ?? (_allFooServices = Kernel.GetAll<IFoo>().ToList()) }
      }
      

      也许你可以选择一个更具体的例子,而不是 Foo。

      【讨论】:

      • 一般的想法是避免服务定位器模式,因为我已经看到它被描述为一种反模式。因此,与其在运行时尝试通过依赖注入器解析服务;您尝试在设置过程中获取服务列表。但是,这样做的问题是,如果您的任何服务想要找到其他服务,您就会遇到上述问题。
      • 我不确定你在这里得到什么 - 你的代码仍然使用服务定位器模式,它只是碰巧使用了构造函数中的模式。它并不能避免服务定位器的问题 - 事实上,正如您发现的那样,它会产生更多问题。
      猜你喜欢
      • 2015-11-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-05-18
      • 2015-03-28
      • 2011-09-26
      • 1970-01-01
      • 2023-04-11
      相关资源
      最近更新 更多