library.inversion.lifecycle.md 6.2 KB

Lifecycle Enum

Overview

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

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

STARTUP

A single instance is created at container initialization time. The same instance is returned for all requests across all scopes. These instances are created eagerly when initialise() is called.

container.register_startup<DatabaseConnection>((s) => new DatabaseConnection());

// After all registrations, initialize startup components
container.initialise();

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

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

Use for:

  • Database connections that must be established at startup
  • Background services that need to start immediately
  • Health-checkable components
  • Any singleton that should fail fast if instantiation fails

Lifecycle Constraints

Singleton/Startup Scope Cannot Resolve Scoped

When resolving from a singleton or startup 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 or startup:

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

Registration Methods by Lifecycle

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

Comparison Table

Lifecycle Instance Count Scope Behavior Memory Initialization
TRANSIENT Per resolve New each time Highest On demand
SCOPED Per scope Shared in scope Medium On demand
SINGLETON Per container Shared globally Lowest On demand
STARTUP Per container Shared globally Lowest Eager (initialise())

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;
});

Startup Database Connection

public class DatabaseConnection : Object {
    public DatabaseConnection() throws DatabaseError {
        // Connect to database - fails fast if connection unavailable
    }
}

container.register_startup<DatabaseConnection>((s) => new DatabaseConnection());

// Will throw if database connection fails, allowing early detection
container.initialise();

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 four instance lifetime behaviors: TRANSIENT for per-resolution instances, SCOPED for per-scope instances, SINGLETON for container-wide single instances, and STARTUP for eagerly-initialized singletons.