依赖注入——如何创建一个Ninject自定义作用域,在对象被处置之前返回相同的对象



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

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

  1. 第一次调用Get()实例化一个新对象并返回它。
  2. 后续调用Get()返回相同的实例。
  3. 对象被处置。
  4. 对象被处置后第一次调用Get()实例化一个新的/秒对象并返回它。
  5. 对Get()的后续调用将返回在步骤4中创建的对象。

EDIT:这个问题实际上很容易解决,使用using providers并让有问题的对象在被处置时引发事件。我很好奇是否有一种方法可以在Ninject中使用作用域来做到这一点,我将把这个问题留在这里,因为Steven的答案非常好。

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

由于竞争条件,您根本无法将对象配置为在处置后更新。想象一下下面的场景:

  1. 线程1从容器请求实例。
  2. 这是第一个请求,容器将创建一个新的实例。线程2从容器请求实例
  3. 容器返回步骤2中创建的实例。
  4. 线程1完成实例并调用Dispose
  5. 线程2开始使用实例,但是实例被丢弃,并抛出异常。

问题是应用程序将获得一个可以被处置的实例的引用。

如果可以的话,尝试通过重新设计应用程序来防止这样做。公开实现IDisposable的服务类型是一个不好的实践,因为IDisposable是一个泄漏的抽象。我个人更倾向于阻止这些服务的任何实现来实现IDisposable。在大多数情况下,重新设计可以防止您必须这样做。

如果您需要使用IDisposable对象,通常的方法是创建并注入创建这些IDisposable对象的工厂。这样,消费者就可以安全地处置这样的对象,而不会出现任何问题。

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

如果你真的想这样做,你可以尝试创建一个做引用计数的装饰器。看看下面的装饰器。它封装了IService并实现了IServiceIService实现IDisposable。装饰器接受一个允许创建实例的Func<IService>委托。对象的创建和处置由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有多个方法)时,实现将中断。

创建一个按预期工作的装饰器是相当困难的。实现这一点的一种简单方法是序列化对对象的访问,但是当您这样做时,您可能希望每个线程使用一个实例。那就容易多了。

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

可以看看Nate Kohari的Cache和Collect文章,了解如何在Ninject 2中完成作用域。

接下来,去看看ninject源代码,看看InRequestScope是如何实现的(包括如何钩入teardown)。计划在2.3-4中进行一些工作来概括它的工作原理,以允许它用于一些复杂的托管场景。

当你看完这两个参考资料后,去邮件列表上问一个问题,你肯定会有一个解决方案。

最新更新