メインコンテンツまでスキップ

Migration from Zenject

This guide is designed to help you migrate an existing project from Zenject (Extenject) to VContainer.

Conceptual Differences

FeatureZenjectVContainerNote
Composition RootMonoInstaller, ScriptableObjectInstallerLifetimeScopeVContainer unifies the concept of Scope and Installer into LifetimeScope.
RegistrationContainer.Bind<T>()builder.Register<T>(Lifetime)VContainer requires explicit Lifetime.
Entry PointsIInitializable, ITickable, etc.IStartable, ITickable, etc.Very similar, but VContainer runs on its own PlayerLoopSystem.
Injection[Inject][Inject]Compatible (VContainer uses its own attribute class).
Sub-ContainersSceneContext, GameObjectContextLifetimeScope (Child)VContainer uses nested LifetimeScopes for everything.

Step-by-Step Migration

1. Replace Installers with LifetimeScope

In Zenject, you likely have a SceneContext and multiple MonoInstallers attached to it.

Zenject:

public class GameInstaller : MonoInstaller
{
public override void InstallBindings()
{
Container.Bind<IFoo>().To<Foo>().AsSingle();
}
}

VContainer: Replace the SceneContext with a GameLifetimeScope (which inherits from LifetimeScope). Move the installation logic into Configure.

public class GameLifetimeScope : LifetimeScope
{
protected override void Configure(IContainerBuilder builder)
{
builder.Register<IFoo, Foo>(Lifetime.Singleton);
}
}

2. Update Registration Syntax

Use the Returning Users Guide to map your bindings.

  • AsSingle() -> Lifetime.Singleton
  • AsCached() -> Lifetime.Scoped
  • AsTransient() -> Lifetime.Transient
  • BindInterfacesAndSelfTo<T>() -> builder.Register<T>(...).AsImplementedInterfaces().AsSelf()

3. Phases and Entry Points

Zenject:

public class MySystem : IInitializable
{
public void Initialize() { ... }
}
// Automatic if BindInterfacesTo is used

VContainer: VContainer does not automatically hook up interfaces unless you use RegisterEntryPoint.

public class MySystem : IStartable // VContainer uses IStartable instead of IInitializable (mostly)
{
public void Start() { ... }
}

// In Configure:
builder.RegisterEntryPoint<MySystem>();

4. Remove [Inject] from Constructors

VContainer automatically injects constructors. You strictly do not need [Inject] on constructors unless you have multiple constructors and need to select one.

Zenject:

public class Foo
{
[Inject] // Remove this!
public Foo(IBar bar) { ... }
}

VContainer:

public class Foo
{
public Foo(IBar bar) { ... } // Works automatically
}

5. Signals

VContainer does not include a SignalBus. We recommend:

6. Factories

Zenject: PlaceholderFactory<T> VContainer: Func<T> or builder.RegisterFactory

If you used heavy custom factories in Zenject:

public class EnemyFactory : PlaceholderFactory<Enemy> { }

In VContainer, just register a Func:

builder.RegisterFactory<Enemy>(container =>
{
return () => container.Instantiate(prefab);
}, Lifetime.Scoped);

Or implement a simple factory class:

public class EnemyFactory
{
readonly IObjectResolver container;
public EnemyFactory(IObjectResolver container) => this.container = container;

public Enemy Create() => container.Instantiate(prefab);
}

Common Pitfalls

  • No "ProjectContext": VContainer uses a generic root LifetimeScope (optional) or VContainerSettings for project-level config.
  • No "Inject" into everything: Zenject often injects into everything that moves. VContainer is stricter. You must explicitly register instances or use InjectGameObject if you want to inject into existing MonoBehaviours.