DI API Reference
A complete catalog of the OnityContainer binding and resolve surface. OnityContainer is a sealed, engine-free (Onity.DI, no UnityEngine), parent-scoped container that implements IResolver and IDisposable. You can use it directly in EditMode tests with no Unity scene:
using Onity.DI;
using OnityContainer container = new OnityContainer();
container.Bind<IClock>().To<SystemClock>().AsSingle();
container.Build();
IClock clock = container.Resolve<IClock>();
A lifetime call (AsSingle() / AsTransient()) is required to actually register a binding; Bind<T>() / To<T>() alone register nothing. The lifetime enum is exactly { Singleton, Transient } — there is no Scoped keyword; a per-scope instance is a child-container AsSingle (see Lifecycle & Scopes).
The resolve machinery is designed to avoid per-call managed allocation (generated activators for marked types, compiled constructor activators on JIT runtimes, pooled argument arrays, cached injection plans, an optional dense-id baked graph). A transient resolve still allocates the instance it returns. On IL2CPP/AOT the container uses generated activators when available and falls back to reflection-based activation otherwise; it constructs correctly either way. OnityContainer.IsCompiledActivationSupported reports only whether runtime Expression.Compile is live, not whether a generated activator exists for a specific type.
Onity has no non-Unity third-party runtime dependencies; the DI core does not use
System.Linq.
Binding entry points
Each entry point returns a fluent builder (except BindInstance and BindFactory, which register immediately). All are instance methods on OnityContainer.
| 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. Supports open generic definitions, e.g. Bind(typeof(IRepo<>)).To(typeof(Repo<>)).AsSingle(). Null contract throws OnityBindingException. |
BindInterfacesAndSelfTo<TConcrete> | BindInterfacesAndSelfTo<TConcrete>() -> MultiTypeBindingBuilder where TConcrete : class | Share one instance across the concrete type and all interfaces it implements. |
BindInterfacesTo<TConcrete> | BindInterfacesTo<TConcrete>() -> MultiTypeBindingBuilder where TConcrete : class | Share one instance across the implemented interfaces only. Throws OnityBindingException if the type implements no interfaces. |
BindInstance<TContract> | BindInstance<TContract>(TContract instance) -> void | Register a pre-built instance. Rejects null with OnityBindingException. |
BindFactory<TValue, TFactory> | BindFactory<TValue, TFactory>() -> void where TFactory : class, IFactory<TValue> | Bind a factory AsSingle via BindInterfacesAndSelfTo. You author the IFactory<TValue> impl. |
BindFactory<TParam, TValue, TFactory> | BindFactory<TParam, TValue, TFactory>() -> void where TFactory : class, IFactory<TParam, TValue> | One-parameter factory variant. |
BindFactory<TParam1, TParam2, TValue, TFactory> | BindFactory<TParam1, TParam2, TValue, TFactory>() -> void where TFactory : class, IFactory<TParam1, TParam2, TValue> | Two-parameter factory variant. |
TypeBindingBuilder<TContract>
Returned by Bind<TContract>().
| API | Signature | Notes |
|---|---|---|
To<TConcrete> | To<TConcrete>() -> TypeBindingBuilder<TContract> where TConcrete : TContract | Set the implementation. Optional — defaults to TContract (self-bind). |
AsSingle | AsSingle() -> TypeBindingBuilder<TContract> | Register as a shared singleton. |
AsTransient | AsTransient() -> TypeBindingBuilder<TContract> | Register as a new instance per resolve. |
NonLazy | NonLazy() -> TypeBindingBuilder<TContract> | Resolve eagerly at Build(). Throws OnityBindingException if called before AsSingle()/AsTransient(). |
MultiTypeBindingBuilder
Returned by BindInterfacesAndSelfTo<T>() / BindInterfacesTo<T>().
| API | Signature | Notes |
|---|---|---|
AsSingle | AsSingle() -> MultiTypeBindingBuilder | One shared instance across all collected contracts. |
AsTransient | AsTransient() -> MultiTypeBindingBuilder | New instance per resolve. |
NonLazy | NonLazy() -> MultiTypeBindingBuilder | Resolve the implementation eagerly at Build(). Throws if no lifetime was set first. |
RuntimeTypeBindingBuilder
Returned by Bind(Type). Handles both closed runtime-typed bindings and open generics.
| API | Signature | Notes |
|---|---|---|
To | To(Type implementationType) -> RuntimeTypeBindingBuilder | Set the implementation. For an open generic contract the implementation must be an open generic definition with the same type-parameter count. Null throws OnityBindingException. |
AsSingle | AsSingle() -> RuntimeTypeBindingBuilder | Singleton. For an open generic, each distinct closed contract gets its own singleton. |
AsTransient | AsTransient() -> RuntimeTypeBindingBuilder | Transient. |
NonLazy | NonLazy() -> RuntimeTypeBindingBuilder | Eager at Build(). Not supported for open generic bindings (the closed type is unknown until resolve) — throws OnityBindingException. |
Resolve and inject
Defined on IResolver (and therefore on OnityContainer). The container always self-resolves OnityContainer and IResolver to the active scope, so a factory can inject IResolver to do manual resolves.
| API | Signature | Notes |
|---|---|---|
Resolve<TService> | Resolve<TService>() -> TService | Resolve by generic type. Throws OnityResolveException if unresolvable. |
Resolve | Resolve(Type serviceType) -> object | Runtime-type overload. Null type throws OnityResolveException. |
TryResolve<TService> | TryResolve<TService>(out TService instance) -> bool | Returns false instead of throwing when unresolvable. |
TryResolve | TryResolve(Type serviceType, out object instance) -> bool | Runtime-type overload. Null type returns false. |
Inject | Inject(object target) -> void | Member-injects an already-constructed object (fields, properties, methods). Null target throws OnityResolveException. |
CanResolve | CanResolve(Type serviceType) -> bool | Reports resolvability without constructing. Accounts for explicit bindings, parent scope, collection element bindings, open generic registrations, and concrete auto-resolve. |
Resolution rules
| Case | Behavior |
|---|---|
| Unbound concrete class | Auto-resolves as an implicit transient. Not shared state — each resolve is a new instance. |
| Unbound interface / abstract / open-generic definition | Throws OnityResolveException. Bind it. |
| Re-binding the same contract | Last binding wins — the previous binding is replaced (no duplicate-binding error). |
Two separate Bind<IFoo>().To<C>() + Bind<IBar>().To<C>() | Produces distinct singletons. Use BindInterfacesAndSelfTo<C>().AsSingle() to share one instance. |
| Constructor selection | A single [Inject] constructor wins; otherwise the greediest public constructor (most parameters). A second [Inject] constructor throws OnityBindingException. |
| Circular dependency | Constructor and member cycles throw OnityResolveException at resolve time (not build time), with the full resolution chain. Break with a factory or IResolver. |
| Closed generics | A closed contract (Repo<int>) resolves if bound explicitly or via an open generic registration. |
| Collection contracts | IEnumerable<T> / IReadOnlyList<T> / IReadOnlyCollection<T> / IList<T> / ICollection<T> / List<T> / T[] are synthesized from every explicit binding of element type T across this scope and its ancestors (ancestors first). An explicit binding of the collection type itself wins over synthesis. |
Build and lifecycle
| API | Signature | Notes |
|---|---|---|
RegisterBuildCallback | RegisterBuildCallback(Action<IResolver> callback) -> void | Run a sync callback during Build(). |
RegisterBuildCallbackAsync | RegisterBuildCallbackAsync(Func<IResolver, Task> callback) -> void | Async callback (no token). |
RegisterBuildCallbackAsync | RegisterBuildCallbackAsync(Func<IResolver, CancellationToken, Task> callback) -> void | Async callback with a cancellation token. |
Build | Build() -> void | Run sync callbacks, finalize bindings, collect lifecycle entry points, and run Initialize(). Idempotent (a second call is a no-op). |
BuildAsync | BuildAsync(CancellationToken cancellationToken = default) -> Task | Run Build() then async callbacks. The result task is cached; on cancel/failure it is re-armed so a later BuildAsync retries. |
Tick | Tick() -> void | Run IOnityTickable.Tick() on collected tickables in registration order. The owning Unity context calls this from Update. No-op before Build(). |
FixedTick | FixedTick() -> void | Run IOnityFixedTickable.FixedTick(). Pumped from the context’s FixedUpdate. |
LateTick | LateTick() -> void | Run IOnityLateTickable.LateTick(). Pumped from the context’s LateUpdate. |
Dispose | Dispose() -> void | Dispose owned singletons in reverse registration order; clear all maps. Resolve/inject after dispose throws OnityResolveException. |
Build callbacks cannot be registered after the build is finalized — both
RegisterBuildCallbackoverloads throwOnityBindingExceptiononceBuild()/BuildAsync()has run.
Lifecycle interfaces
Implement on a singleton (or bound instance) to be wired up automatically — binding the type is enough, no explicit entry-point registration. Transient bindings are not ticked (no single stable instance).
| Interface | Member | Called |
|---|---|---|
IOnityInitializable | Initialize() | Once, at the end of Build(), in binding-registration order. Lifecycle singletons are created eagerly here. |
IOnityTickable | Tick() | Every frame from the context’s Update (via OnityContainer.Tick). |
IOnityFixedTickable | FixedTick() | Every physics step from FixedUpdate (via OnityContainer.FixedTick). |
IOnityLateTickable | LateTick() | Late each frame from LateUpdate (via OnityContainer.LateTick). |
Injection sites — [Inject]
InjectAttribute targets Constructor | Field | Property | Method. Constructor injection is preferred; use the others only when a constructor cannot.
| Site | Rule |
|---|---|
| Constructor | A single [Inject] constructor is selected; otherwise the greediest public constructor. Multiple [Inject] constructors throw OnityBindingException. |
| Field | Private fields are allowed. |
| Property | A setter is required (private is fine). An [Inject] get-only property or an [Inject] indexer throws OnityBindingException. |
| Method | Runs after constructor, fields, and properties. Cannot be generic — a generic [Inject] method throws OnityBindingException. |
| Static members | Never injected (an [Inject] static member is silently ignored). |
Member-injection order: base class → derived class, and within a type fields → properties → methods.
Diagnostics and binding sources
For editor tooling and debugging. None of these are needed for normal binding/resolve.
| API | Signature | Notes |
|---|---|---|
OnityContainer.DiagnosticsCollectionEnabled | static bool { get; set; } | Enable per-resolve timing/count collection. |
OnityContainer.IsCompiledActivationSupported | static bool { get; } | True when the runtime Expression.Compile activation path is live (usually Editor and Mono players). False on AOT/IL2CPP does not prevent generated activators from being used; it only means ungenerated constructors fall back to reflection. |
GetDiagnostics | GetDiagnostics() -> OnityContainerDiagnostics | Snapshot of binding/plan/owned-provider counts and whether a parent exists. |
GetBindingDiagnostics | GetBindingDiagnostics(List<OnityBindingDiagnostics> results) -> void | Fill a caller-supplied list with per-binding rows (impl type, contracts, lifetime, resolve count, timings). |
PushBindingSource | PushBindingSource(string sourceName) -> IDisposable | Label subsequent bindings (shown by inspector tooling) for the current thread; dispose to pop. |
TryGetBindingSource | TryGetBindingSource(Type contractType, out OnityBindingSourceInfo sourceInfo) -> bool | Look up source metadata for a contract in this scope or an ancestor. |
TryGetLocalBindingSource | TryGetLocalBindingSource(Type contractType, out OnityBindingSourceInfo sourceInfo) -> bool | Same, but this scope only. |
Constructor: new OnityContainer(OnityContainer parent = null) — pass a parent to create a child scope.
Factories — Onity.Factory
Author one of these interfaces and bind it with BindFactory to pass a runtime value into an injected object. There is no container.Instantiate<T>(args) and no fluent factory body (.FromMethod etc.).
| Interface | Member |
|---|---|
IFactory<TValue> | TValue Create() |
IFactory<TParam, TValue> | TValue Create(TParam param) |
IFactory<TParam1, TParam2, TValue> | TValue Create(TParam1 p1, TParam2 p2) |
Exceptions
Two sealed exception types, both in Onity.DI.
| Type | Raised by |
|---|---|
OnityResolveException | Resolve/inject failures: unbound contract, circular dependency (resolve-time), failed construction, null service type, null inject target, resolve after dispose. |
OnityBindingException | Binding/config failures: null/non-assignable/abstract implementation, no-interface BindInterfacesTo, empty contract list, no accessible constructor, multiple [Inject] constructors, setterless/indexer [Inject] property, generic [Inject] method, callback registration after build, NonLazy before a lifetime. |
For the full message-to-fix table, see the AI Usage Guide error section. Narrative usage lives in the Dependency Injection guide.
Not shipped (Zenject parity gaps): no
WhenInjectedInto, noWithId, noInstantiate(args). Use a typed factory or distinct contracts instead.