Skip to main content

Lifetimes

Lifetime management controls how long a service's instances will live (from instantiation to disposal) and how they will be reused between resolution requests.

info

Choosing the right lifetime helps you avoid captive dependencies.

Default lifetime

When you are not specifying a lifetime during registration, Stashbox will use the default lifetime. By default, it's set to Transient, but you can override it with the .WithDefaultLifetime() container configuration option.

You can choose either from the pre-defined lifetimes defined on the Lifetimes static class or use a custom lifetime.

var container = new StashboxContainer(options => options
.WithDefaultLifetime(Lifetimes.Transient));

Transient lifetime

A new instance is created for each resolution request. If a transient is referred by multiple consumers in the same resolution tree, each will get a new instance.

container.Register<IJob, DbBackup>(options => options
.WithLifetime(Lifetimes.Transient));
info

Transient services are not tracked for disposal by default, but this feature can be turned on with the .WithDisposableTransientTracking() container configuration option. When it's enabled, the current scope on which the resolution request was initiated takes the responsibility to track and dispose transient services.

Singleton lifetime

A single instance is created and reused for each resolution request and injected into each consumer.

note

Singleton services are disposed when the container (root scope) is being disposed.

container.Register<IJob, DbBackup>(options => options
.WithLifetime(Lifetimes.Singleton));

Scoped lifetime

A new instance is created for each scope, which will be returned for every resolution request initiated on the given scope. It's like a singleton lifetime within a scope.

note

Scoped services are disposed when their scope is being disposed.

container.Register<IJob, DbBackup>(options => options
.WithLifetime(Lifetimes.Scoped));

using var scope = container.BeginScope();
IJob job = scope.Resolve<IJob>();

Named scope lifetime

It is the same as scoped lifetime, except the given service will be selected only when a scope with the same name initiates the resolution request.

You can also let a service define its own named scope. During registration, this scope can be referred to by its name upon using a named scope lifetime.

container.Register<IJob, DbBackup>(options => options
.InNamedScope("DbScope"));

using var scope = container.BeginScope("DbScope");
IJob job = scope.Resolve<IJob>();
note

Services with named scope lifetime are disposed when the related named scope is being disposed.

Per-request lifetime

The requested service will be reused within the whole resolution request. A new instance is created for each individual request .

container.Register<IJob, DbBackup>(options => options
.WithPerRequestLifetime());

Per-scoped request lifetime

The requested service will behave like a singleton, but only within a scoped dependency request. This means every scoped service will get a new exclusive instance that will be used by its sub-dependencies as well.

container.Register<IJob, DbBackup>(options => options
.WithPerScopedRequestLifetime());

Auto lifetime

The requested service's lifetime will align to the lifetime of its dependencies. When the requested service has a dependency with a higher lifespan, this lifetime will inherit that lifespan up to a given boundary.

container.Register<IJob, DbBackup>(options => options
.WithAutoLifetime(Lifetimes.Scoped /* boundary lifetime */));

If the requested service has auto lifetime with a scoped boundary and it has only transient dependencies, it'll inherit their transient lifetime.

container.Register<ILogger, Logger>();

container.Register<IJob, DbBackup>(options => options
.WithAutoLifetime(Lifetimes.Scoped /* boundary lifetime */));

// job has transient lifetime.
var job = container.Resolve<IJob>();

When there's a dependency with higher lifespan than the given boundary, the requested service will get the boundary lifetime.

container.RegisterSingleton<ILogger, Logger>();

container.Register<IJob, DbBackup>(options => options
.WithAutoLifetime(Lifetimes.Scoped /* boundary lifetime */));

// job has scoped lifetime.
var job = container.Resolve<IJob>();

Custom lifetime

If you'd like to use a custom lifetime, you can create your implementation by inheriting either from FactoryLifetimeDescriptor or from ExpressionLifetimeDescriptor, depending on how do you want to manage the service instances.

  • ExpressionLifetimeDescriptor: With this, you can build your lifetime with the expression form of the service instantiation.

    class CustomLifetime : ExpressionLifetimeDescriptor
    {
    protected override Expression ApplyLifetime(
    Expression expression, // The expression which describes the service creation
    ServiceRegistration serviceRegistration,
    ResolutionContext resolutionContext,
    Type requestedType)
    {
    // Lifetime managing functionality
    }
    }
  • FactoryLifetimeDescriptor: With this, you can build your lifetime based on a pre-compiled factory delegate used for service instantiation.

    class CustomLifetime : FactoryLifetimeDescriptor
    {
    protected override Expression ApplyLifetime(
    Func<IResolutionScope, object> factory, // The factory used for service creation
    ServiceRegistration serviceRegistration,
    ResolutionContext resolutionContext,
    Type requestedType)
    {
    // Lifetime managing functionality
    }
    }

Then you can use your lifetime like this:

container.Register<IJob, DbBackup>(options => options.WithLifetime(new CustomLifetime()));