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:
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 excellent.
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:
Dispose
.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:
Dispose
multiple times breaks the decorator.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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With