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

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 typed IFactory<...> with BindFactory.
  • Fluent factory bodies (FromMethod, FromIFactory) and Func<...> factory registration: a factory is a C# class.
  • [Inject(Optional = true)]: bind the dependency, or use TryResolve in 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: use BindInterfacesAndSelfTo or BindInterfacesTo.