Skip to content
Peter Csajtai edited this page May 14, 2019 · 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.Register<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 Drizzts 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 whichs 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 its parents 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.Register<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 its actual disposal
container.Register<IDrow, Drizzt>(context => context.WithFinalizer(t => t.CallGuenhwyvarBack()));

Named lifetime scopes

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

container.Register<IDrow, Drizzt>(context => context.InNamedScope("DefendOfMithralHall"));
container.Register<IDrow, Berginyon>(context => context.InNamedScope("AttackOfMithralHall"));

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

using(var root = container.BeginScope("DefendOfMithralHall"))
{
    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("AttackOfMithralHall"))
    {
        var drow3 = scope3.Resolve<IDrow>(); // Berginyon will be resolved
    }
}

Service as a scope

You can configure a service to behave like a named scope, which means that upon its resolution a new dedicated named scope will be opened for managing its dependencies.

container.Register<IEvent, AttackOfMithralHall>(context => context.DefinesScope("AttackMithralHall"));
container.Register<IEvent, DefendOfMithralHall>(context => context.DefinesScope("DefendOfMithralHall"));

The lifetime of the internal named scope will be attached to that ones lifetime, which was used to create the service.

Binding an instance to a lifetime scope

You have the option to bind an already instantiated service to a lifetime scope. which means that the service's lifetime will be tracked by the scope and will be disposed when the scope is being disposed.

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

You can disable the lifetime tracking by passing true for the withoutDisposalTracking parameter, then only the strong reference to the instance will be dropped when the scope is being disposed.

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.Register<IDrow, Drizzt>();
    
    IDrow drizzt;
    IBarbarian wulfgar;
    
    //Create a child scope
    using(var child = container.CreateChildContainer())
    {
        //Register to the child container
        child.Register<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...
    }
}