library.inversion.injection.md 4.7 KB

Field Injection

Overview

Inversion provides field injection via inject<T>() and inject_all<T>() functions. These functions resolve dependencies during object construction when called within field initializers. The injection context is automatically established during container resolution.

Namespace

Inversion

Injection Functions

inject<T>() → T

Resolves a single dependency of type T. Returns null if resolution fails or no injection context exists.

public class UserService : Object {
    private ILogger logger = inject<ILogger>();
    
    public void greet(string name) {
        this.logger.log(@"Hello, $name!");
    }
}

inject_all<T>() → Lot<T>

Resolves all registered implementations of type T. Returns empty collection if resolution fails.

public class MultiHandler : Object {
    private Lot<IHandler> handlers = inject_all<IHandler>();
    
    public void process_all(string request) {
        handlers.iterate((h) => h.handle(request));
    }
}

try_inject<T>() → T?

Attempts to resolve a single dependency. Returns null if no injection context exists or resolution fails. Use when dependency is optional.

public class OptionalDeps : Object {
    private ICache? cache = try_inject<ICache>();  // May be null
}

try_inject_all<T>() → Lot<T>

Attempts to resolve all implementations. Returns empty collection if no injection context exists.

public class OptionalHandlers : Object {
    private Lot<IHandler> handlers = try_inject_all<IHandler>();
}

Injection Context

How It Works

  1. Container resolves a type via Scope.resolve<T>()
  2. create_with_injection_context() establishes thread-local context
  3. Factory creates instance; field initializers execute
  4. Field initializers call inject<T>() which accesses current context
  5. Context is cleaned up after instance creation

InjectionContext Class

Holds the current injection state:

Property Type Description
registration Registration Current registration being resolved
requested_type Type Type requested for resolution
scope Scope Active scope for resolution
error Error? Error if resolution failed
stack_registrations HashSet<Registration> Registrations in current stack
previous_context InjectionContext? Parent context for nested resolutions

get_injection_context() → InjectionContext

Gets the current injection context. Throws error if not initialized.

try_get_injection_context() → InjectionContext?

Gets current context or null if not available.

Cycle Detection

The injection context tracks the resolution stack to detect circular dependencies:

// This will throw ContainerError.CYCLE_DETECTED
public class CircularA : Object {
    private CircularB b = inject<CircularB>();
}

public class CircularB : Object {
    private CircularA a = inject<CircularA>();
}

Error message includes the cycle path:

Cycle detected during dependency injection: CircularA => CircularB => CircularA ad infinitum.

Nested Injection

Dependencies can have their own injected dependencies:

public class ApiService : Object {
    private Controller controller = inject<Controller>();  // Controller has its own injections
}

public class Controller : Object {
    private ILogger logger = inject<ILogger>();
    private IRepository repo = inject<IRepository>();
}

Thread Safety

Injection contexts are thread-local. Each thread has its own context stack, enabling concurrent resolution in different threads.

Usage Patterns

Required Dependencies

Use inject<T>() for required dependencies:

public class OrderService : Object {
    private IOrderRepository orders = inject<IOrderRepository>();
    private IPaymentGateway payments = inject<IPaymentGateway>();
}

Optional Dependencies

Use try_inject<T>() for optional dependencies:

public class CacheService : Object {
    private ICache? cache = try_inject<ICache>();
    
    public string? get(string key) {
        if (cache == null) return null;
        return cache.get(key);
    }
}

Multiple Implementations

Use inject_all<T>() for plugin/handler patterns:

public class ValidationService : Object {
    private Lot<IValidator> validators = inject_all<IValidator>();
    
    public bool validate(Object obj) {
        foreach (var v in validators) {
            if (!v.validate(obj)) return false;
        }
        return true;
    }
}

Field injection in Inversion uses inject<T>() and inject_all<T>() functions in field initializers to resolve dependencies automatically during container-managed object creation, with cycle detection and thread-local context management.