Object Scopes

Alexander Levinson edited this page Jun 11, 2016 · 27 revisions

In many cases, the same instance of a type needs to be used throughout your application. The common way to achieve this goal is via the Singleton pattern. For example:

sealed class Shogun 
{
    public static readonly Shogun Instance = new Shogun();
    private Shogun() { }
    public void RuleWithIronFist() 
    {
        ...
    }
}

The problem with this is you basically have to lock down your type. Doing things this way will make your code end up rigid and inflexible. In particular, it'll be very difficult to unit test code that's implemented this way.

Ninject makes it easy to re-use instances that are already created, without having to implement anything via code. All you need to do is tell Ninject to Bind the class in a singleton scope.

kernel.Bind<Shogun>().ToSelf().InSingletonScope();

Of course, this behaviour will only work for instances requested from the Kernel. (Resolving instances is explained in the next section.)

There are four built-in scopes available in Ninject, and a number of others delivered via Extensions such as Ninject.Extensions.NamedScope:

Scope Triggered By Meaning Disposal
Transient .InTransientScope(), .InXXXScope() missing, implicit ToSelf()-Binding of concrete types, .InParentScope() where parent is .InTransientScope(). A new instance of the type will be created each time one is requested. This is the default scope if none is specified. Lifetime is not managed by the Kernel (the Scope object is null) and will never be Disposed
Singleton .InSingletonScope() or .ToConstant() Only a single instance of the type will be created, and the same instance will be returned for each subsequent request. Disposed when the Kernel is Disposed.
Thread .InThreadScope() One instance of the type will be created per thread. Disposed when underlying Thread object is garbage collected. [See implications of the Cache and Collect system described here
Request .InRequestScope() One instance of the type will be created for each Web Request. See the Ninject.Web.Common InRequestScope article for more information before using this. Disposable instances are Disposed at end of request processing.
Named, Call, Parent .InNamedScope(), .InCallScope(), .InParentScope() Supplied by an extension. See Ninject.Extensions.NamedScope extension for more information before using this. As for Custom - instances are tracked and either get Disposed as their scoping object is GC'd or you can Kernel.Release().
Custom .InScope() See below. Disposed via Cache and Collect. Can be Disposed preemptively via Kernel.Release().

Custom scopes

You can also easily define your own scopes using the .InScope(Func<IContext,object> selectScope) method. Note that the releasing of instances is subject to the implications of the Cache and Collect system described here

Here's a simple test demonstrating how a custom scope works:

using Xunit;
using Ninject;

namespace NinjectCustomScopes
{
    public class ScopeObject { }

    public static class ProcessingScope
    {
        public static ScopeObject Current { get; set; }
    }

    public class NinjectCustomScopeExample
    {
        public class TestService { }

        [Fact]
        public static void Test()
        {
            var kernel = new StandardKernel();
            kernel.Bind<TestService>().ToSelf().InScope(x => ProcessingScope.Current);

            var ScopeA = new ScopeObject();
            var ScopeB = new ScopeObject();

            ProcessingScope.Current = ScopeA;
            var testA1 = kernel.Get<TestService>();
            var testA2 = kernel.Get<TestService>();

            Assert.Same(testA1, testA2);

            ProcessingScope.Current = ScopeB;
            var testB = kernel.Get<TestService>();

            Assert.NotSame(testB, testA1);

            ProcessingScope.Current = ScopeA;
            var testA3 = kernel.Get<TestService>();

            Assert.Same(testA1, testA3);
        }
    }
}

Continue reading: Modules and the Kernel