如何创建一个Ninject自定义作用域,它返回相同的对象,直到该对象被处置?

时间:2011-04-26 01:11:58

标签: dependency-injection scope inversion-of-control ninject

在Ninject中,在单例范围内声明绑定意味着每次都会返回相同的对象。永远只能有一个对象。

我想要的是一次返回一个对象。换句话说:

  1. 第一次调用Get()实例化一个新对象并将其返回。
  2. 对Get()的后续调用将返回相同的实例。
  3. 物品被丢弃。
  4. 在放置对象后第一次调用Get()实例化一个新的/第二个对象并返回它。
  5. 对Get()的后续调用将返回在步骤4中创建的对象。
  6. 编辑:使用providers解决此问题实际上非常简单,并且在处置时让相关对象引发事件。我很好奇是否有办法在Ninject中使用范围进行此操作,并且会在此留下这个问题,因为史蒂文的答案非常好。

2 个答案:

答案 0 :(得分:6)

由于您希望在多线程应用程序中使用此构造并希望跨线程重用相同的实例(正如您在注释中所暗示的那样),因此您将无法通过配置DI容器来解决此问题。 / p>

由于竞争条件,您无法在处置后配置要续订的对象。想象一下以下场景:

  1. 线程1从容器中请求实例。
  2. 这是第一个请求,容器将创建一个新实例。
  3. 线程2从容器中请求实例
  4. 容器返回在步骤2中创建的实例。
  5. 线程1与实例一起完成并调用Dispose
  6. 线程2开始使用实例,但实例被处理掉,并抛出异常。
  7. 问题是应用程序将获得对可以处理的实例的引用。

    如果可以的话,尽量通过重新设计应用程序来防止这样做。公开实现IDisposable的服务类型是一种不好的做法,因为IDisposable是一个漏洞抽象。我个人的偏好甚至是阻止这些服务的任何实现来实现IDisposable。在大多数情况下,重新设计可以防止您不必这样做。

    如果需要使用IDisposable个对象,通常的做法是创建和注入创建这些IDisposable个对象的工厂。通过这种方式,消费者可以毫无问题地安全地处理这样的物体。

    这里的一般问题是很难创建实现IDisposable的对象,这些对象实际上是线程安全的。

    如果你真的想要这个,你可以尝试创建一个引用计数的装饰器。在下面的装饰器中查找实例。它包装IService并实现IServiceIService实施IDisposable。装饰者需要Func<IService&gt;允许创建实例的委托。对象的创建和处理受lock语句的保护,并且装饰器计算调用者对它的引用。在最后一个消费者处理装饰器之后,它将处理该对象并创建一个新对象。

    public class ScopedServiceDecorator : IService
    {
        private readonly object locker = new object();
        private Func<IService> factory;
        private IService currentInstance;
        private int referenceCount;
    
        public ScopedServiceDecorator(Func<IService> factory)
        {
            this.factory = factory;
        }
        public void SomeOperation()
        {
            IService instance;
            lock (this.locker)
            {
                instance = this.GetInstance();
                this.referenceCount++;
            }
    
            instance.SomeOperation();
        }
    
        public void Dispose()
        {
            IService instance = null;
    
            lock (this.locker)
            {
                this.referenceCount--;
    
                if (this.referenceCount == 0)
                {
                    instance = this.wrappedService;
                    this.wrappedService = null;
                }
            }
    
            // Dispose the object outside the lock for performance.
            if (instance != null)
            {
                instance.Dispose();
            }
        }
    
        private IService GetInstance()
        {
            if (this.wrappedService == null)
            {
                this.wrappedService = this.factory();
            }
    
            return this.wrappedService;
        }
    }
    

    请注意,由于以下原因,此实施仍存在缺陷:

    1. 多次调用Dispose会破坏装饰者。
    2. 当消费者多次致电SomeOperation时(或IService有多种方法),实施将会中断。
    3. 创建一个按预期运行的装饰器非常困难。一种简单的方法是序列化对象的访问,但是当你这样做时,你可能想要为每个线程使用一个实例。那会容易得多。

      我希望这会有所帮助。

答案 1 :(得分:1)

我知道这已经解决了但是...... @ Steven的回答并没有指出Ninject中有一个InScope机制来解决你正在寻找的方面。

请看Nate Kohari的Cache and Collect文章,了解如何在Ninject 2中完成范围界定。

接下来,查看ninject源代码,了解InRequestScope的实现方式(包括如何连接拆卸)。有一些工作计划用于2.3-4,以概括它如何工作以允许它用于一些复杂的托管场景。

当您查看这两个参考文献时,请在ninject邮件列表上提问,您肯定会有解决方案。