library.inversion.lifecycle.md 4.7 KB

Lifecycle Enum

Overview

Lifecycle defines instance lifetime behavior for registered components. Three lifecycles are supported: transient (new instance per resolve), scoped (one instance per scope), and singleton (one instance per container).

Namespace

Inversion

Enum Declaration

public enum Lifecycle

Values

TRANSIENT

A new instance is created every time the component is requested.

container.register_transient<MyService>((s) => new MyService());

var scope = container.create_scope();
var a = scope.resolve<MyService>();  // New instance
var b = scope.resolve<MyService>();  // Different instance
// a != b

Use for:

  • Lightweight, stateless services
  • Objects with no shared state
  • Per-operation handlers

SCOPED

A single instance is created per scope. Within the same scope, the same instance is returned for each request.

container.register_scoped<UserSession>((s) => new UserSession());

var scope1 = container.create_scope();
var scope2 = container.create_scope();

var a1 = scope1.resolve<UserSession>();  // Instance A
var a2 = scope1.resolve<UserSession>();  // Same Instance A
// a1 == a2

var b1 = scope2.resolve<UserSession>();  // Instance B (different)
// a1 != b1

Use for:

  • Per-request context (web applications)
  • Unit-of-work patterns
  • Transaction scopes

SINGLETON

A single instance is created for the lifetime of the container. The same instance is returned for all requests across all scopes.

container.register_singleton<Configuration>((s) => new Configuration());

var scope1 = container.create_scope();
var scope2 = container.create_scope();

var a = scope1.resolve<Configuration>();  // Instance A
var b = scope2.resolve<Configuration>();  // Same Instance A
// a == b

Use for:

  • Application-wide configuration
  • Shared caches
  • Connection pools
  • Logging services

Lifecycle Constraints

Singleton Scope Cannot Resolve Scoped

When resolving from a singleton context, scoped registrations cannot be resolved:

// This throws ILLEGAL_LIFECYCLE_COMBINATION
// A singleton cannot depend on a scoped service
public class SingletonService : Object {
    private ScopedService scoped = inject<ScopedService>();  // Error at runtime!
}

container.register_singleton<SingletonService>((s) => new SingletonService());
container.register_scoped<ScopedService>((s) => new ScopedService());

var scope = container.create_scope();
scope.resolve<SingletonService>();  // Throws!

Scope-Local Registrations

Scope-local registrations cannot be singletons:

// Compile-time precondition failure
scope.register_local<MyService>((s) => new MyService(), Lifecycle.SINGLETON);
// Error: requires (lifecycle != Lifecycle.SINGLETON)

Registration Methods by Lifecycle

Method Lifecycle
register_transient<T>() TRANSIENT
register_scoped<T>() SCOPED
register_singleton<T>() SINGLETON
register<T>(..., lifecycle) Specified

Comparison Table

Lifecycle Instance Count Scope Behavior Memory
TRANSIENT Per resolve New each time Highest
SCOPED Per scope Shared in scope Medium
SINGLETON Per container Shared globally Lowest

Usage Examples

Configuration Singleton

public class AppConfig : Object {
    public string database_path { get; set; }
}

container.register_singleton<AppConfig>((s) => {
    var config = new AppConfig();
    config.database_path = "/data/app.db";
    return config;
});

Request-Scoped Context

public class RequestContext : Object {
    public string request_id { get; construct set; }
    public User? current_user { get; set; }
}

// In request handler
void handle_request(Request req) {
    var scope = container.create_scope();
    scope.register_local_scoped<RequestContext>((s) => {
        var ctx = new RequestContext();
        ctx.request_id = req.id;
        return ctx;
    });
    
    var handler = scope.resolve<RequestHandler>();
    handler.process();
}

Transient Handlers

public interface IRequestHandler : Object {
    public abstract void handle(Request req);
}

container.register_transient<CreateUserHandler>((s) => new CreateUserHandler())
    .as_type(typeof(IRequestHandler));
container.register_transient<DeleteUserHandler>((s) => new DeleteUserHandler())
    .as_type(typeof(IRequestHandler));

// Each resolution creates new handler instances
var handlers = scope.resolve_all<IRequestHandler>();

The Lifecycle enum defines three instance lifetime behaviors: TRANSIENT for per-resolution instances, SCOPED for per-scope instances, and SINGLETON for container-wide single instances.