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

ant*_*ony 6 scope dependency-injection ninject inversion-of-control

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

我想要的是一次返回一个对象.换一种说法:

  1. 第一次调用Get()实例化一个新对象并返回它.
  2. 对Get()的后续调用返回相同的实例.
  3. 物体被丢弃.
  4. 在处理对象之后对Get()的第一次调用实例化一个新的/第二个对象并返回该对象.
  5. 对Get()的后续调用将返回在步骤4中创建的对象.

编辑:使用提供程序解决此问题实际上相当简单,并且在处理时让相关对象引发事件.我很好奇是否有办法在Ninject中使用范围进行此操作,并且会在此留下这个问题,因为史蒂文的答案非常好.

Ste*_*ven 6

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

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

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

问题是应用程序将获得对可以处理的实例的引用.

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

如果需要使用IDisposable对象,通常的方法是创建和注入创建这些IDisposable对象的工厂.这样,消费者可以安全地处理这样的物体,而没有任何问题.

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

如果你真的想要这个,你可以尝试创建一个引用计数的装饰器.在下面的装饰器中查找实例.它包装IService和实现IService.IService实施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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

  1. Dispose多次调用打破了装饰者.
  2. 当消费者SomeOperation多次调用(或者IService有多种方法)时,实现将会中断.

创建一个按预期运行的装饰器非常困难.一种简单的方法是序列化对象的访问,但是当你这样做时,你可能想要为每个线程使用一个实例.这会容易得多.

我希望这有帮助.