From VContainer

Use these tables to translate a VContainer project. VContainer registers with builder.Register<TImpl>(Lifetime.Singleton).As<TInterface>() inside a LifetimeScope; Onity binds with container.Bind<TInterface>().To<TImpl>().AsSingle() inside a MonoInstaller that a context runs. The three lifetimes map one to one (Lifetime.Scoped is AsScoped()), entry points become lifecycle interfaces that the container collects without a registration call, and IAsyncStartable becomes IOnityAsyncInitializable. The behaviors that differ: BindInstance rejects null, circular dependencies surface at resolve time rather than at build, and there is no Func<> factory registration. Everything here is verified against the Onity 0.8.2 source; an API that is not listed does not exist.

Contents:

Registration

VContainer Onity Notes
builder.Register<Impl>(Lifetime.Singleton).As<IService>() container.Bind<IService>().To<Impl>().AsSingle() One instance in the declaring scope, shared with children.
builder.Register<Impl>(Lifetime.Scoped).As<IService>() container.Bind<IService>().To<Impl>().AsScoped() One instance per resolving scope, built with that scope’s dependencies and disposed with it.
builder.Register<Impl>(Lifetime.Transient).As<IService>() container.Bind<IService>().To<Impl>().AsTransient() A new instance per resolve.
builder.Register<Impl>(Lifetime.Singleton) (self) container.Bind<Impl>().AsSingle() To defaults to the contract.
builder.Register<Impl>(Lifetime.Singleton).As<IA>().As<IB>().AsSelf() container.BindInterfacesAndSelfTo<Impl>().AsSingle() One instance under the concrete type and every interface.
builder.Register<Impl>(Lifetime.Singleton).AsImplementedInterfaces() container.BindInterfacesTo<Impl>().AsSingle() Interfaces only. Throws OnityBindingException when Impl has no interfaces.
builder.RegisterInstance(instance).As<IService>() container.BindInstance<IService>(instance) Caller-owned. A null instance throws OnityBindingException.
builder.RegisterInstance(instance).AsImplementedInterfaces().AsSelf() container.BindInstance<IA>(instance); container.BindInstance<Impl>(instance); One BindInstance per contract; the object is the same.
builder.Register<Impl>(Lifetime.Singleton).As<IService>().Keyed("x") container.Bind<IService>().To<Impl>().WithId("x").AsSingle() Consume with [Inject(Id = "x")] or Resolve<IService>("x").
builder.Register<IService, Impl>(Lifetime.Singleton) container.Bind<IService>().To<Impl>().AsSingle() Same contract-first shape.
builder.RegisterBuildCallback(resolver => ...) container.RegisterBuildCallback(resolver => ...) Also RegisterBuildCallbackAsync(Func<IResolver, Task>) and the token form.
builder.RegisterComponentInHierarchy<T>(), RegisterComponentInNewPrefab<T>(), RegisterComponentOnNewGameObject<T>() none Bind the component instance after you obtain it, or use BindScriptableObject and BindPooledFactory (Onity.Unity.Installers).
builder.RegisterEntryPoint<T>() container.BindInterfacesAndSelfTo<T>().AsSingle() where T implements a lifecycle interface Binding is the registration; see Entry point lifecycle.
Registering the same contract twice the last binding wins No error in either library.

Two separate Bind<IA>().To<C>() and Bind<IB>().To<C>() calls create two C instances. VContainer’s As<IA>().As<IB>() sharing is BindInterfacesAndSelfTo<C>() or BindInterfacesTo<C>().

Injection

Onity.DI.InjectAttribute targets constructors, fields, properties, methods and parameters.

VContainer Onity Notes
Constructor injection Constructor injection Identical and preferred.
The greediest resolvable constructor The public constructor with the most parameters A non-public constructor is used only when no public one exists.
[Inject] public Foo(IBar bar) [Inject] public Foo(IBar bar) The marked constructor wins. Two marked constructors throw OnityBindingException.
[Inject] IBar m_bar; [Inject] private IBar m_bar; Field injection, private allowed. Static members are never injected.
[Inject] public IBar Bar { get; set; } [Inject] public IBar Bar { get; set; } A setter is required.
[Inject] public void Construct(IBar bar) [Inject] private void Construct(IBar bar) Runs after the constructor, fields and properties. Cannot be generic.
[Key("x")] on a parameter [Inject(Id = "x")] on the parameter, field or property Selects a WithId binding.
resolver.Inject(instance) container.Inject(instance) Member-injects an existing object.
resolver.InjectGameObject(gameObject) context.InjectGameObject(root) On the owning OnityContext.

Member injection order is base class, then derived class; within a type fields, then properties, then methods.

Resolution

VContainer Onity Notes
resolver.Resolve<IFoo>() container.Resolve<IFoo>() Throws OnityResolveException when unresolvable.
resolver.Resolve(typeof(IFoo)) container.Resolve(typeof(IFoo)) Runtime-type form.
resolver.Resolve<IFoo>("x") (keyed) container.Resolve<IFoo>("x") Identified resolve; TryResolve<IFoo>("x", out foo) for the safe form.
resolver.TryResolve<IFoo>(out var foo) container.TryResolve<IFoo>(out IFoo foo) Returns bool; out is null on a miss.
inject IObjectResolver inject IResolver or OnityContainer Both resolve to the active scope.
resolver.Resolve<IEnumerable<IFoo>>() container.Resolve<IEnumerable<IFoo>>() Or any of the other six collection shapes; see below.
Unregistered type throws VContainerException Unbound interface or abstract type throws OnityResolveException An unbound concrete class resolves as an implicit transient.

Factories

VContainer registers Func<...> factories with RegisterFactory. Onity has no Func<> factory and no Instantiate(args): author an IFactory<...> (Onity.Factory) and bind it with BindFactory, which binds it with BindInterfacesAndSelfTo<TFactory>().AsSingle().

VContainer Onity Notes
builder.RegisterFactory<TValue>(() => ...) container.BindFactory<TValue, TFactory>() where TFactory : IFactory<TValue> Resolve IFactory<TValue> and call Create().
builder.RegisterFactory<TParam, TValue>(param => ...) container.BindFactory<TParam, TValue, TFactory>() where TFactory : IFactory<TParam, TValue> Resolve IFactory<TParam, TValue> and call Create(param).
builder.RegisterFactory<T1, T2, TValue>(...) container.BindFactory<T1, T2, TValue, TFactory>() where TFactory : IFactory<T1, T2, TValue> Two parameters is the largest arity.
RegisterFactory with a resolver-bound delegate the factory’s constructor takes the services The container constructs the factory.
WithParameter(...) on a registration an IFactory<TParam, TValue> Per-call values do not go through constructor injection.
using Onity.Factory;            // IFactory<TParam, TValue>

public sealed class EnemyFactory : IFactory<EnemySpawnRequest, Enemy>
{
    private readonly IClock m_clock;

    public EnemyFactory(IClock clock)   // services come through the constructor
    {
        m_clock = clock;
    }

    public Enemy Create(EnemySpawnRequest request)
    {
        return new Enemy(request.Id, request.Level, m_clock);
    }
}

Bind and use (fragment): container.BindFactory<EnemySpawnRequest, Enemy, EnemyFactory>(); then container.Resolve<IFactory<EnemySpawnRequest, Enemy>>().Create(request). Prefab pools are BindPooledFactory(prefab, parent, defaultCapacity, maxSize); see Factories and Pooling.

Collection injection

VContainer collects every registration of a contract when you inject IEnumerable<T> or IReadOnlyList<T>. Onity does the same for IEnumerable<T>, IReadOnlyList<T>, IReadOnlyCollection<T>, IList<T>, ICollection<T>, List<T> and T[], gathered from this scope and its ancestors, ancestors first. Fragment:

container.Bind<IDamageRule>().To<CritRule>().AsSingle();
container.Bind<IDamageRule>().To<ArmorRule>().AsSingle();
using System.Collections.Generic;

public sealed class DamagePipeline
{
    private readonly IReadOnlyList<IDamageRule> m_rules;

    public DamagePipeline(IReadOnlyList<IDamageRule> rules)   // VContainer: IEnumerable<IDamageRule>
    {
        m_rules = rules;
    }
}

A plain Resolve<IDamageRule>() returns the last binding; only a collection-typed request gathers all of them.

Entry point lifecycle

VContainer runs entry points through RegisterEntryPoint. Onity collects a singleton, a scoped binding or a bound instance that implements a lifecycle interface (Onity.DI) at Build(); the owning context pumps the ticks. Transients are not collected.

VContainer Onity Notes
IInitializable.Initialize(), IStartable.Start() IOnityInitializable.Initialize() Once, at the end of Build(), in binding-registration order.
IAsyncStartable.StartAsync(CancellationToken) IOnityAsyncInitializable.InitializeAsync(CancellationToken) A ValueTask. Awaited in BuildAsync, one initializer at a time; the token is the scope’s LifetimeToken.
ITickable.Tick() IOnityTickable.Tick() From the context’s Update.
IFixedTickable.FixedTick() IOnityFixedTickable.FixedTick() From FixedUpdate.
ILateTickable.LateTick() IOnityLateTickable.LateTick() From LateUpdate.
IPostInitializable, IPostStartable, IPostTickable, IPostLateTickable none Order within a phase follows binding-registration order; bind the later service later.
builder.RegisterEntryPointExceptionHandler(...) none A faulted InitializeAsync ends BuildAsync; the context logs it and ReadyTask faults.
using Onity.DI;                 // IOnityInitializable, IOnityTickable

public sealed class WaveDirector : IOnityInitializable, IOnityTickable
{
    public void Initialize()
    {
        // Runs once at Build().
    }

    public void Tick()
    {
        // Runs every Update from the owning context.
    }
}

Binding it is the whole wiring (fragment): container.BindInterfacesAndSelfTo<WaveDirector>().AsSingle();.

Open generic registration

Fragment:

container.Bind(typeof(IRepository<>)).To(typeof(Repository<>)).AsSingle();
IRepository<PlayerProfile> profiles = container.Resolve<IRepository<PlayerProfile>>();

The first resolve of a closed contract builds and caches it as a normal binding. NonLazy() is not supported on an open generic binding. On IL2CPP the closed type must survive stripping.

Scopes and lifecycle

VContainer Onity Notes
LifetimeScope (root, DontDestroyOnLoad) ProjectContext (Onity.Unity.Contexts) Prefab at Assets/Resources/Onity/ProjectContext.prefab, created with Onity/Contexts/Create ProjectContext Prefab, loaded before the first scene.
LifetimeScope in a scene SceneContext Child of the ProjectContext when one exists.
LifetimeScope on a prefab, EnqueueParent, CreateChild GameObjectContext, or new OnityContainer(parent) in code The child inherits the parent’s bindings; a child binding shadows the parent only inside the child.
Configure(IContainerBuilder builder) MonoInstaller.InstallBindings(OnityContainer container) (Onity.Unity.Installers) Assigned to the context’s Installers list.
autoRun, autoInjectGameObjects Auto Inject Hierarchy and Run Async Build Callbacks on the context Both default on.
Lifetime.Scoped AsScoped() Shared within a resolving scope, distinct across sibling scopes.
IDisposable services disposed on scope dispose container.Dispose(); a context disposes in OnDestroy Cancels the scope token, then disposes child scopes, scoped instances and singletons in reverse registration order.
Build-time validation of the whole graph resolve-time validation Build() runs NonLazy bindings and the callbacks; missing or circular bindings throw OnityResolveException at resolve time.

Every context binds the container, IResolver, IOnityScopeLifetime, the context, MessageBroker and OnityEventHub, so these need no installer line. VContainer’s IObjectResolver injected into a class becomes IResolver.

Errors

VContainer throws VContainerException for registration and resolution problems. Onity splits them: OnityBindingException for configuration (null instance, non-assignable implementation, multiple [Inject] constructors, setterless, indexer or generic [Inject] members, callbacks registered after build, NonLazy before a lifetime) and OnityResolveException for resolution (unbound contract, circular dependency, failed construction, ambiguous conditional bindings, null target, use after dispose). Both live in Onity.DI.

Not supported

VContainer feature Do this in Onity
Func<...> factory registration and RegisterFactory Author an IFactory<...> and BindFactory.
RegisterInstance(null) Pass a non-null instance.
RegisterComponentInHierarchy, RegisterComponentInNewPrefab, RegisterComponentOnNewGameObject Bind the obtained component with BindInstance, or use BindScriptableObject and BindPooledFactory.
WithParameter on a registration An IFactory<TParam, TValue>.
IPostInitializable and the other post-phase entry points Binding-registration order within the phase.
RegisterEntryPointExceptionHandler Handle inside the service; a faulted InitializeAsync faults ReadyTask.
Build-time graph validation Resolve-time exceptions; NonLazy() surfaces construction errors at Build().

Features that ship and need no workaround: AsScoped() for Lifetime.Scoped, keyed bindings (WithId, [Inject(Id = ...)]), conditional bindings (WhenInjectedInto), collection injection, open generic registration, the lifecycle interfaces including IOnityAsyncInitializable, Unbind and Rebind, and pools (OnityObjectPool<T>, PrefabComponentPool<T>, BindPooledFactory).

Lifetime and async

VContainer’s LifetimeScope disposes its container; Onity adds a scope token that is canceled before the services are disposed. Services inject IOnityScopeLifetime for it, MonoBehaviours call GetScopeCancellationToken(), and async startup moves from IAsyncStartable or a UniTask entry point to IOnityAsyncInitializable; code that must wait for the scope awaits OnityContext.WaitReadyAsync(). Read Lifecycle and Scopes before you move startup code, and From UniTask if the project also uses UniTask.