DI API
The catalog of the OnityContainer surface (Onity.DI): every binding entry point and builder member, the resolve and inject calls, build and lifecycle, the scope lifetime members, the injection sites, the composition helpers from the other assemblies, the factory contracts and the exceptions. Use it to look up a signature; the Dependency Injection guide explains when to use each call.
OnityContainer is sealed, engine-free and parent-scoped, and implements IResolver, IOnityScopeLifetime and IDisposable. It runs in EditMode tests with no scene:
using Onity.DI; // OnityContainer
using OnityContainer container = new OnityContainer();
container.Bind<IClock>().To<GameClock>().AsSingle();
container.Build();
IClock clock = container.Resolve<IClock>();
A lifetime call (AsSingle(), AsScoped(), AsTransient()) or FromSubContainerResolve(...) registers a binding; Bind<T>() and To<T>() alone register nothing. The Lifetime enum in Onity.Core stays { Singleton, Transient }; AsScoped() is a builder lifetime.
The resolve path avoids per-call managed allocation: generated activators for marked types, compiled constructor activators on JIT runtimes, pooled argument arrays, cached injection plans and a dense-id baked graph after Build(). A transient resolve still allocates the instance it returns. On IL2CPP the container uses generated activators when available and reflection otherwise; it constructs correctly either way. See Performance and IL2CPP.
Contents:
- Binding entry points
- Resolve and inject
- Build and lifecycle
- Scope lifetime
- Injection sites
- Composition helpers
- Diagnostics and binding sources
- Factories
- Exceptions
- Not shipped
Binding entry points
Instance methods on OnityContainer. Each returns a fluent builder, except BindInstance, BindFactory and Unbind, which act at once.
| API | Signature | Notes |
|---|---|---|
Bind<TContract> | Bind<TContract>() -> TypeBindingBuilder<TContract> | Start a single-contract binding. Chain To<TConcrete>(), then a lifetime. |
Bind (runtime type) | Bind(Type contractType) -> RuntimeTypeBindingBuilder | Bind from a runtime Type, closed or an open generic definition such as typeof(IRepository<>). A null type throws OnityBindingException. |
BindInterfacesAndSelfTo<TConcrete> | BindInterfacesAndSelfTo<TConcrete>() -> MultiTypeBindingBuilder where TConcrete : class | One instance under the concrete type and every interface it implements. |
BindInterfacesTo<TConcrete> | BindInterfacesTo<TConcrete>() -> MultiTypeBindingBuilder where TConcrete : class | One instance under the implemented interfaces only. Throws OnityBindingException when the type implements no interfaces. |
BindInstance<TContract> | BindInstance<TContract>(TContract instance) -> void | Register a caller-owned instance; the container never disposes it. Null throws OnityBindingException. |
BindInstance<TContract> (identified) | BindInstance<TContract>(TContract instance, object id) -> void | The same under an identifier. A null id selects the unkeyed binding. |
BindFactory<TValue, TFactory> | BindFactory<TValue, TFactory>() -> void where TFactory : class, IFactory<TValue> | Binds the factory with BindInterfacesAndSelfTo<TFactory>().AsSingle(). You author the IFactory<TValue>. |
BindFactory<TParam, TValue, TFactory> | BindFactory<TParam, TValue, TFactory>() -> void where TFactory : class, IFactory<TParam, TValue> | One-parameter form. |
BindFactory<TParam1, TParam2, TValue, TFactory> | BindFactory<TParam1, TParam2, TValue, TFactory>() -> void where TFactory : class, IFactory<TParam1, TParam2, TValue> | Two-parameter form, the largest arity. |
Rebind<TContract> | Rebind<TContract>(object id = null) -> TypeBindingBuilder<TContract> | Replace the local bindings for the contract and id when the new lifetime is selected; the old bindings stay until then. Works after Build(). |
Rebind (runtime type) | Rebind(Type contractType, object id = null) -> RuntimeTypeBindingBuilder | The same from a runtime Type, including open generic definitions. |
Unbind<TContract> | Unbind<TContract>(object id = null) -> bool | Remove the local bindings for the exact contract and id, including conditional ones; true when something was removed. Parent registrations are unchanged. A removed singleton stops ticking but stays owned until disposal. |
Unbind (runtime type) | Unbind(Type contractType, object id = null) -> bool | The same from a runtime Type. |
TypeBindingBuilder
Returned by Bind<TContract>() and Rebind<TContract>().
| API | Signature | Notes |
|---|---|---|
To<TConcrete> | To<TConcrete>() -> TypeBindingBuilder<TContract> where TConcrete : TContract | Set the implementation. Optional; defaults to TContract (self-bind). |
WithId | WithId(object id) -> TypeBindingBuilder<TContract> | Identify the binding; ids compare by value. Before the lifetime. Consume with [Inject(Id = ...)] or Resolve<T>(id). |
WhenInjectedInto<TConsumer> | WhenInjectedInto<TConsumer>() -> TypeBindingBuilder<TContract> | Restrict injection to this consumer type and its derived types. Before the lifetime. |
AsSingle | AsSingle() -> TypeBindingBuilder<TContract> | One instance in the declaring scope, shared with children. |
AsScoped | AsScoped() -> TypeBindingBuilder<TContract> | One instance per resolving scope, disposed with that scope. |
AsTransient | AsTransient() -> TypeBindingBuilder<TContract> | A new instance per resolve. |
FromSubContainerResolve | FromSubContainerResolve(Action<OnityContainer> install) -> TypeBindingBuilder<TContract> | Export the contract from a child container installed once per requesting scope. Cannot be combined with To. |
NonLazy | NonLazy() -> TypeBindingBuilder<TContract> | Resolve at Build(). Throws OnityBindingException before a lifetime or on a conditional binding. |
MultiTypeBindingBuilder
Returned by BindInterfacesAndSelfTo<T>() and BindInterfacesTo<T>().
| API | Signature | Notes |
|---|---|---|
WithId | WithId(object id) -> MultiTypeBindingBuilder | Identify every contract. Before the lifetime. |
WhenInjectedInto<TConsumer> | WhenInjectedInto<TConsumer>() -> MultiTypeBindingBuilder | Restrict every contract to the consumer. Before the lifetime. |
AsSingle | AsSingle() -> MultiTypeBindingBuilder | One shared instance across all collected contracts. |
AsScoped | AsScoped() -> MultiTypeBindingBuilder | One instance per resolving scope across the contracts. |
AsTransient | AsTransient() -> MultiTypeBindingBuilder | A new instance per resolve. |
NonLazy | NonLazy() -> MultiTypeBindingBuilder | Resolve at Build(). Throws OnityBindingException before a lifetime or on a conditional binding. |
RuntimeTypeBindingBuilder
Returned by Bind(Type) and Rebind(Type, id). Handles closed runtime-typed bindings and open generics.
| API | Signature | Notes |
|---|---|---|
To | To(Type implementationType) -> RuntimeTypeBindingBuilder | Set the implementation. For an open generic contract it must be an open generic definition with the same type-parameter count. Null throws OnityBindingException. |
WithId | WithId(object id) -> RuntimeTypeBindingBuilder | As on TypeBindingBuilder. |
WhenInjectedInto<TConsumer> | WhenInjectedInto<TConsumer>() -> RuntimeTypeBindingBuilder | As on TypeBindingBuilder. |
AsSingle | AsSingle() -> RuntimeTypeBindingBuilder | For an open generic, each distinct closed contract gets its own singleton. |
AsScoped | AsScoped() -> RuntimeTypeBindingBuilder | One closed instance per resolving scope. |
AsTransient | AsTransient() -> RuntimeTypeBindingBuilder | A new instance per resolve. |
FromSubContainerResolve | FromSubContainerResolve(Action<OnityContainer> install) -> RuntimeTypeBindingBuilder | Export a closed contract from a child container per requesting scope. |
NonLazy | NonLazy() -> RuntimeTypeBindingBuilder | Resolve at Build(). Not supported for an open generic binding (the closed type is unknown until resolve); throws OnityBindingException. |
Resolve and inject
Defined on IResolver, which OnityContainer implements. The container always resolves OnityContainer and IResolver to the active scope, so a factory injects IResolver for manual resolves.
| API | Signature | Notes |
|---|---|---|
Resolve<TService> | Resolve<TService>() -> TService | Throws OnityResolveException when unresolvable. |
Resolve | Resolve(Type serviceType) -> object | Runtime-type form. A null type throws OnityResolveException. |
Resolve<TService> (identified) | Resolve<TService>(object id) -> TService | Resolve a WithId binding. A null id selects the unkeyed binding; a missing non-null id does not construct a service implicitly. |
Resolve (identified) | Resolve(Type serviceType, object id) -> object | Runtime-type form of the identified resolve. |
TryResolve<TService> | TryResolve<TService>(out TService instance) -> bool | False instead of throwing when unresolvable. |
TryResolve | TryResolve(Type serviceType, out object instance) -> bool | Runtime-type form. A null type returns false. |
TryResolve<TService> (identified) | TryResolve<TService>(object id, out TService instance) -> bool | False when the identified binding is missing; construction and ambiguity errors still throw. |
TryResolve (identified) | TryResolve(Type serviceType, object id, out object instance) -> bool | Runtime-type form. |
Inject | Inject(object target) -> void | Member-inject an already constructed object (fields, properties, methods). A null target throws OnityResolveException. |
CanResolve | CanResolve(Type serviceType) -> bool | On OnityContainer only. Reports resolvability without constructing: explicit bindings here or in an ancestor, collection element bindings, open generic registrations and concrete auto-resolve. |
Resolution rules
| Case | Behavior |
|---|---|
| Unbound concrete class | Resolves as an implicit transient: a new instance per resolve, never shared. |
| Unbound interface, abstract class or open generic definition | Throws OnityResolveException. |
| Same contract bound twice | The last binding wins; there is no duplicate-binding error. |
Bind<IFoo>().To<C>() plus Bind<IBar>().To<C>() | Two distinct C instances. Share one with BindInterfacesAndSelfTo<C>().AsSingle(). |
| Constructor selection | A single [Inject] constructor wins; otherwise the public constructor with the most parameters. A non-public constructor is used only when no public one exists. A second [Inject] constructor throws OnityBindingException. |
| Circular dependency | Constructor and member cycles throw OnityResolveException at resolve time with the full chain. Break the cycle with a factory or IResolver. |
| Conditional bindings | A matching WhenInjectedInto binding wins over the unconditional one in the same scope; two matching conditional bindings throw OnityResolveException. A local unconditional binding shadows conditions in ancestor scopes. |
| Closed generics | A closed contract resolves when bound explicitly or through an open generic binding; the closed implementation is cached after the first resolve. |
| Collection contracts | IEnumerable<T>, IReadOnlyList<T>, IReadOnlyCollection<T>, IList<T>, ICollection<T>, List<T> and T[] are synthesized from every explicit binding of T in this scope and its ancestors, ancestors first. An explicit binding of the collection type wins over synthesis; implicit auto-resolved concretes are not members. |
Build and lifecycle
| API | Signature | Notes |
|---|---|---|
RegisterBuildCallback | RegisterBuildCallback(Action<IResolver> callback) -> void | Runs during Build(). |
RegisterBuildCallbackAsync | RegisterBuildCallbackAsync(Func<IResolver, Task> callback) -> void | Async callback without a token. |
RegisterBuildCallbackAsync | RegisterBuildCallbackAsync(Func<IResolver, CancellationToken, Task> callback) -> void | Async callback that receives the scope token. |
Build | Build() -> void | Runs the sync callbacks, finalizes the bindings, collects the lifecycle instances and runs Initialize(). Idempotent. |
BuildAsync | BuildAsync(CancellationToken cancellationToken = default) -> Task | Runs Build(), the async callbacks, then every IOnityAsyncInitializable one at a time, each starting on the context the build started on. Callbacks and initializers receive LifetimeToken, linked with the caller’s token when one is passed. The task is cached; a canceled or faulted run is not, so the next call re-runs the async callbacks unless all completed and resumes at the first incomplete initializer. |
Tick | Tick() -> void | Runs IOnityTickable.Tick() on the collected tickables in registration order, then on installed child scopes. The context calls it from Update. No-op before Build(). |
FixedTick | FixedTick() -> void | Runs IOnityFixedTickable.FixedTick(). From the context’s FixedUpdate. |
LateTick | LateTick() -> void | Runs IOnityLateTickable.LateTick(). From the context’s LateUpdate. |
IsDisposed | bool { get; } | True after Dispose(). |
Dispose | Dispose() -> void | Cancels LifetimeToken, then disposes installed child scopes, scoped instances and owned singletons in reverse registration order, and clears every map. Resolve or inject afterwards throws OnityResolveException. |
Both RegisterBuildCallback overloads and RegisterBuildCallbackAsync throw OnityBindingException once the build is finalized. Constructor: new OnityContainer(OnityContainer parent = null); pass a parent to create a child scope.
Lifecycle interfaces
Implement on a singleton, a scoped binding or a bound instance; binding the type is the whole wiring. Transient bindings are not collected. Lifecycle singletons are created at Build().
| Interface | Member | Called |
|---|---|---|
IOnityInitializable | void Initialize() | Once, at the end of Build(), in binding-registration order. |
IOnityAsyncInitializable | ValueTask InitializeAsync(CancellationToken) | Once, in BuildAsync() after the async build callbacks, one at a time in binding-registration order. A context is ready after every one completed. |
IOnityTickable | void Tick() | Every frame, through OnityContainer.Tick() from the context’s Update. |
IOnityFixedTickable | void FixedTick() | Every physics step, through FixedTick() from FixedUpdate. |
IOnityLateTickable | void LateTick() | Late each frame, through LateTick() from LateUpdate. |
Scope lifetime
| API | Namespace | Signature | Notes |
|---|---|---|---|
OnityContainer.LifetimeToken | Onity.DI | CancellationToken { get; } | Canceled first in Dispose(). Created on first request; linked to the parent’s token; a disposed container returns a canceled token. Thread-safe. |
IOnityScopeLifetime | Onity.DI | CancellationToken Token { get; }, bool IsDisposed { get; } | The scope as an injectable contract. OnityContainer implements it; every Unity context binds it to its own container. |
AddTo | Onity.DI (OnityScopeLifetimeExtensions) | AddTo<TDisposable>(this TDisposable disposable, IOnityScopeLifetime scope) -> TDisposable where TDisposable : IDisposable | Disposes the disposable when the scope ends, at once if it already ended. The scope keeps it until then. Null arguments throw ArgumentNullException. |
OnityContext.Container | Onity.Unity.Contexts | OnityContainer { get; } | The container the context owns. |
OnityContext.LifetimeToken | Onity.Unity.Contexts | CancellationToken { get; } | The container’s token; before Awake creates the container, the context’s destroy token; after destroy, a canceled token. Main thread only. |
OnityContext.IsReady | Onity.Unity.Contexts | bool { get; } | True after the sync and configured async build steps, including every IOnityAsyncInitializable. |
OnityContext.ReadyTask | Onity.Unity.Contexts | Task { get; } | Completes when the context is ready; canceled when the context is destroyed first. |
OnityContext.WaitReadyAsync | Onity.Unity.Contexts | WaitReadyAsync(CancellationToken cancellationToken = default) -> OnityTask | The OnityTask form of ReadyTask; resumes on the main thread. The token cancels this wait only. Await it once. |
OnityContext.InjectGameObject | Onity.Unity.Contexts | InjectGameObject(GameObject root) -> void | Member-injects every MonoBehaviour under root that this context owns. |
GetScopeCancellationToken | Onity.Unity.Async (OnityScopeTokenExtensions) | GetScopeCancellationToken(this Component component) -> CancellationToken | The LifetimeToken of the nearest OnityContext on the component or its parents; without one, the component’s destroy token. Main thread only. |
GetCancellationTokenOnDestroy | Onity.Unity.Async (OnityTaskLifetimeExtensions) | GetCancellationTokenOnDestroy(this MonoBehaviour), (this GameObject), (this Component) -> CancellationToken | The destroy token of the object. |
Injection sites
InjectAttribute (Onity.DI) targets Constructor | Field | Property | Method | Parameter and has one property, object Id, which selects a WithId binding. On constructors and methods, annotate the individual parameters to select identifiers.
| Site | Rule |
|---|---|
| Constructor | A single [Inject] constructor is selected; otherwise the public constructor with the most parameters, or a non-public one when no public constructor exists. Multiple [Inject] constructors throw OnityBindingException. |
| Field | Private fields are allowed. |
| Property | A setter is required (private is fine). A get-only or indexer [Inject] property throws OnityBindingException. |
| Method | Runs after the constructor, fields and properties. Cannot be generic; a generic [Inject] method throws OnityBindingException. |
| Parameter | [Inject(Id = ...)] on a constructor or method parameter selects an identified binding for that parameter. |
| Static members | Never injected. |
Member injection order: base class, then derived class; within a type, fields, then properties, then methods.
Composition helpers
Extension methods on OnityContainer from the other assemblies. The helpers that create a primitive hand its ownership to the scope: the container disposes it after it cancels LifetimeToken. The helpers that bind an object you pass in leave it caller-owned.
| API | Namespace | Signature | Binds | Disposed by |
|---|---|---|---|---|
BindReactiveProperty | Onity.Composition | BindReactiveProperty<T>(this OnityContainer, T initialValue) -> ReactiveProperty<T> | ReactiveProperty<T>, IReadOnlyReactiveProperty<T> | the scope |
BindSubject | Onity.Composition | BindSubject<T>(this OnityContainer) -> Subject<T> | Subject<T> | the scope |
DeclareMessage | Onity.Composition | DeclareMessage<T>(this OnityContainer) -> MessageChannel<T> | MessageChannel<T>, IPublisher<T>, ISubscriber<T>; a standalone channel, not the broker’s | the scope |
DeclareAsyncMessage | Onity.Composition | DeclareAsyncMessage<T>(this OnityContainer) -> AsyncMessageChannel<T> | AsyncMessageChannel<T>, IAsyncPublisher<T>, IAsyncSubscriber<T>; standalone | the scope |
BindAsyncReactiveProperty | Onity.Unity.Async | BindAsyncReactiveProperty<T>(this OnityContainer, T initialValue) -> OnityAsyncReactiveProperty<T> | OnityAsyncReactiveProperty<T>, IOnityAsyncReactiveProperty<T>, IOnityReadOnlyAsyncReactiveProperty<T> | the scope; disposal cancels pending WaitAsync calls |
BindMessageChannel | Onity.Unity.Messaging | BindMessageChannel<TMessage>(this OnityContainer) -> void | IPublisher<TMessage>, ISubscriber<TMessage> from the scope’s IMessageBroker, which must be resolvable when the helper runs (a context binds it) | the broker’s owner |
BindPooledFactory (prefab) | Onity.Unity.Installers | BindPooledFactory<TComponent>(this OnityContainer, TComponent prefab, Transform parent = null, int defaultCapacity = 16, int maxSize = 512) -> void where TComponent : Component | IPool<TComponent> (a new PrefabComponentPool<TComponent>), IFactory<TComponent> (PooledFactory<TComponent>) | the scope; inactive clones are destroyed |
BindPooledFactory (pool) | Onity.Unity.Installers | BindPooledFactory<TValue>(this OnityContainer, IPool<TValue> pool) -> void | IPool<TValue> (the instance), IFactory<TValue> | the caller; pool.AddTo(container) hands it to the scope |
BindScriptableObject | Onity.Unity.Installers | BindScriptableObject<TContract>(this OnityContainer, TContract asset) -> void where TContract : ScriptableObject | member-injects the asset, then TContract as an instance | the caller |
BindScriptableObject (contract) | Onity.Unity.Installers | BindScriptableObject<TContract, TAsset>(this OnityContainer, TAsset asset) -> void where TContract : class, TAsset : ScriptableObject, TContract | member-injects the asset, then TContract as an instance | the caller |
A null prefab, pool or asset throws OnityBindingException.
Diagnostics and binding sources
For Editor tooling and debugging; none is needed for normal binding or resolving.
| API | Signature | Notes |
|---|---|---|
OnityContainer.DiagnosticsCollectionEnabled | static bool { get; set; } | Enables per-resolve timing and count collection. |
OnityContainer.IsCompiledActivationSupported | static bool { get; } | True when the runtime Expression.Compile activation path is live (the Editor and Mono players). False on IL2CPP does not prevent generated activators from being used; ungenerated constructors fall back to reflection. |
GetDiagnostics | GetDiagnostics() -> OnityContainerDiagnostics | Counts of explicit and implicit bindings, cached plans and owned providers, and whether a parent exists. |
GetBindingDiagnostics | GetBindingDiagnostics(List<OnityBindingDiagnostics> results) -> void | Fills the list with one row per binding: implementation, contracts, lifetime, resolve count and timings. |
PushBindingSource | PushBindingSource(string sourceName) -> IDisposable | Labels subsequent bindings on the current thread for inspector tooling; dispose to pop. Contexts label their default bindings and each installer step this way. |
TryGetBindingSource | TryGetBindingSource(Type contractType, out OnityBindingSourceInfo sourceInfo) -> bool | Source metadata for a contract in this scope or an ancestor. |
TryGetLocalBindingSource | TryGetLocalBindingSource(Type contractType, out OnityBindingSourceInfo sourceInfo) -> bool | The same, this scope only. |
The Editor windows that read these live under Onity/Tools/Monitor and Onity/Tools/Container Diagnostics.
Factories
Author one of these contracts (Onity.Factory) and bind it with BindFactory. The container constructs the factory, so it can take services in its constructor; only runtime values travel through Create.
| Interface | Member |
|---|---|
IFactory<TValue> | TValue Create() |
IFactory<TParam, TValue> | TValue Create(TParam param) |
IFactory<TParam1, TParam2, TValue> | TValue Create(TParam1 param1, TParam2 param2) |
The pooled adapters PooledFactory<TValue>, PooledFactory<TParam, TValue> and PooledFactory<TParam1, TParam2, TValue> (Onity.Pooling) implement these contracts over an IPool<T> or IParameterizedPool<T>; see Factories and Pooling.
Exceptions
Two sealed exception types in Onity.DI.
| Type | Raised by |
|---|---|
OnityResolveException | Resolve and inject failures: unbound contract, circular dependency, failed construction, ambiguous conditional bindings, null service type, null inject target, resolve after dispose. |
OnityBindingException | Binding and configuration failures: null contract, implementation or instance, non-assignable or abstract implementation, BindInterfacesTo on a type with no interfaces, no accessible constructor, multiple [Inject] constructors, setterless or indexer [Inject] property, generic [Inject] method, callback registration after build, NonLazy before a lifetime or on a conditional or open generic binding, a FromSubContainerResolve installer that does not bind its export. |
The message-to-fix table is in the AI Usage Guide.
Not shipped
Instantiate(args): use a typedIFactory<...>withBindFactory.- Fluent factory bodies (
FromMethod,FromIFactory) andFunc<...>factory registration: a factory is a C# class. [Inject(Optional = true)]: bind the dependency, or useTryResolvein glue code.- A null instance binding:
BindInstance(null)throws. - Signals: use the messaging pillar; see Messaging API.
- A multi-type
Bind(typeof(IA), typeof(IB))overload: useBindInterfacesAndSelfToorBindInterfacesTo.