开发者

How to create a Ninject custom scope that returns the same object until that object is disposed?

开发者 https://www.devze.com 2023-02-28 21:25 出处:网络
In Ninject, declaring a binding in singleton scope means that the same object will be returned every time. There can only be one object, ever.

In Ninject, declaring a binding in singleton scope means that the same object will be returned every time. There can only be one object, ever.

What I would like is to return one object at a time. In other words:

  1. The first call to Get() instantiates a new object and returns it.
  2. Subsequent calls to Get() return the same instance.
  3. The object is disposed.
  4. The first call to Get() after the object was disposed instantiates a new/second object and returns that.
  5. Subsequent calls to Get() return the object created in step 4.

EDIT: This problem is actually rather simple to solve using using providers and having the object in question raise an event when disposed. I was curious if there was a way to do this using scopes in Ninject, and will leave this question here because Steven's answer is exc开发者_开发百科ellent.


Since you want to use this construct in a multi-threaded application and want to reuse the same instance across threads (as you imply in your comment), you will not be able to solve this problem by configuring your DI container.

You simply can't configure the object to be renewed after disposal, because of race conditions. Imagine the following scenario:

  1. Thread 1 requests an instance from the container.
  2. This is the first request and the container will create a fresh instance.
  3. Thread 2 requests an instance from the container
  4. The container returns the instance created in step 2.
  5. Thread 1 is done with the instance and calls Dispose.
  6. Thread 2 starts using the instance, but the instance is disposed, and throws an exception.

The problem is that the application will get a reference to an instance that can be disposed.

Try to prevent doing this by redesigning your application if you can. It's a bad practice to expose service types that implement IDisposable, because IDisposable is a leaky abstraction. My personal preference is even to prevent any implementations of these services to implement IDisposable. In most scenarios a redesign can prevent you from having to do this.

If you need to use IDisposable objects, the usual way to do this is to create and inject factories that create these IDisposable objects. This way the consumer can safely dispose such an object, without any problem.

The general problem here is that it is hard to create objects that implement IDisposable, that are actually thread-safe.

If you really want this, you can try creating a decorator that does reference counting. Look for instance at the decorator below. It wraps an IService and implements IService. IService implements IDisposable. The decorator takes a Func<IService> delegate that allows creation of instances. Creation and disposal of objects is protected by a lock statement and the and the decorator counts the references to it by callers. It will dispose the object and create a new one, after the last consumer disposed the decorator.

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;
    }
}

Please note that this implementation is still flawed, because of the following reasons:

  1. Calling Dispose multiple times breaks the decorator.
  2. When consumers call the SomeOperation multiple times (or the IService has multiple methods) the implementation will break.

It is pretty hard to create a decorator that functions as expected. One simple way of doing this is by serializing access to the object, but when you do this, you probably want to use a single instance per thread. That would be much easier.

I hope this helps.


I know this is solved but... @Steven's answer doesnt point out that there's a InScope mechanism in Ninject that addresses aspects of what you're looking for.

Have a look at Nate Kohari's Cache and Collect article re how scoping can be done in Ninject 2.

Next, go look at the ninject source and see how InRequestScope is implemented (including how the teardown is hooked in). There's some work planned for 2.3-4 to generalise how that works to permit it to be used for some complex hosting scenarios.

When you've looked at these two references, go ask a question on the ninject mailing list and you'll definitely have a solution.

0

精彩评论

暂无评论...
验证码 换一张
取 消