Skip to content
Peter Csajtai edited this page Mar 19, 2018 · 19 revisions

Lifetime scope

A lifetime scope is a unit within your scoped services are being reused and shared by their consumers. It can be interpreted as the unit-of-work pattern where the lifetime scope encapsulates a given unit and the services required for the work are being resolved from it, and when the work finishes the services are being disposed with it.

class Drizzt : IDrow
{
    [Dependency("Icingdeath")]
    public IWeapon RightHand { get; set; }
	
    [Dependency("Twinkle")]
    public IWeapon LeftHand { get; set; }
}

//Register Twinkle as a scoped service
container.RegisterScoped<IWeapon, Twinkle>("Twinkle");

//Register Icingdeath as a singleton service
container.RegisterSingleton<IWeapon, Icingdeath>("Icingdeath");

//Register Drizzt as a transient service
container.RegisterType<IDrow, Drizzt>();

//This will create a scoped instance within the root scope, so it can be interpreted as a singleton
var twinkle = container.Resolve<IWeapon>("Twinkle");

using(var scope = container.BeginScope())
{
    //A new 'Twinkle' instance will be injected into Drizzt's left hand,
    //cause it was registered as a scoped service and was resolved from a lifetime scope.
    //'Icingdeath' will be resolved from the root scope, 
    //cause it was registered as a singleton.
    var drizzt = scope.Resolve<IDrow>();
}

Nested lifetime scopes

Lifetime scopes are nestable, just think about the Singleton lifetime which's service is always resolved from the root scope, so it's shared between all scopes.

using(var scope1 = container.BeginScope())
{
    using(var scope2 = scope1.BeginScope())
    {
        //etc...
    }
}

You have the option to attach a given scope to it's parent lifetime, so when the parent is being disposed, the child will be disposed along with it.

using(var scope1 = container.BeginScope())
{
    var scope2 = scope1.BeginScope(attachToParent: true);
    //...

} // scope2 will be disposed as well

Disposal

The lifetime scope tracks the resolved objects and disposes them (only if they are implements the IDisposable interface) when the scope is being disposed. You can control this behavior with some configuration:

//This will include transient objects into the disposal tracking
var container = new StashboxContainer(config => config.WithDisposableTransientTracking());

//This will exclude the given registration from the disposal tracking
container.RegisterType<IDrow, Drizzt>(context => context.WithoutDisposalTracking());

Cleanup delegate

During the registration of a service, you can set a custom finalizer delegate, which will be called when the scope used to instantiate your registered type is being disposed:

//The given delegate will be called when the scope is being disposed, before the actual service's disposal
container.RegisterType<IDrow, Drizzt>(context => context.WithFinalizer(t => t.CallGuenhwyvarBack()));

Named lifetime scopes

When you are registering a service, you have the option to set it's lifetime to a NamedScopeLifetime by doing the following:

container.RegisterType<IDrow, Drizzt>(context => context.InNamedScope("DefendMithralHall"));
container.RegisterType<IDrow, Berginyon>(context => context.InNamedScope("AttackMithralHall"));

Then when you are opening a new scope you can define by it's name that which types you'd like to use:

using(var root = container.BeginScope("DefendMithralHall"))
{
    var drow = root.Resolve<IDrow>(); // Drizzt will be resolved
    
    using(var scope2 = root.BeginScope())
    {
        var drow2 = scope2.Resolve<IDrow>(); // Drizzt will be resolved because of the named parent scope
    }

    using(var scope3 = root.BeginScope("AttackMithralHall"))
    {
        var drow3 = scope3.Resolve<IDrow>(); // Berginyon will be resolved
    }
}

Injecting objects into a lifetime scope

You also have the ability to put additional services into a given lifetime scope by calling the PutInstanceInScope() method:

using(var scope = container.BeginScope())
{
    scope.PutInstanceInScope<IDrow>(drizzt);
}

Services injected in this way will be disposed by the scope, except if you set the withoutDisposalTracking parameter to true:

using(var scope = container.BeginScope())
{
    scope.PutInstanceInScope<IDrow>(drizzt, true);
}

Child scopes

With a child scope you can build up a parent-child relationship between containers. This means you can have a different subset of services present in child and parent containers, if something is missing from a child the parent will be asked to resolve the service:

var container = new StashboxContainer();

//Create a child scope
var child = container.CreateChildContainer();

The child container maintains the lifetime of those services only which were registered into it:

using(var container = new StashboxContainer(config => config.WithDisposableTransientTracking()))
{
    //Register to the parent container
    container.RegisterType<IDrow, Drizzt>();
    
    IDrow drizzt;
    IBarbarian wulfgar;
    
    //Create a child scope
    using(var child = container.CreateChildContainer())
    {
        //Register to the child container
        child.RegisterType<IBarbarian, Wulfgar>();
        
        drizzt = child.Resolve<IDrow>();
        wulfgar = child.Resolve<IBarbarian>();
    } //At the end of the scope only Wulfgar will be disposed because only him was maintained by the child container
}

Nested child scopes

Child scopes are also nestable:

using(var child1 = container.CreateChildContainer())
{
    using(var child2 = child1.CreateChildContainer())
    {
        //etc...
    }
}