Skip to main content

Comparing to Zenject

Zenject is awesome. However, VContainer has the following advantages:

  • Good performance.
  • Most parts of reflections and assertions are isolated to the Container's build stage.
  • Easy to read implementation.
  • VContainer has carefully selected features and does not register data-oriented objects in the container or actively inject the View component. This prevents the DI declaration from becoming overly complex.
    • Zenject is often used to inject into dynamic or datacentric objects, but this is complicated
    • In VContainer, injection of MonoBehaviour is recommended over injection into MonoBehaviour.
    • Zenject looks up all GameObjects in reflection when the scene starts, but this operation is expensive; VContainer does not do this.

Code size (v1.3.0)#

API difference#

Basic#

ZenjectVContainer
Container.Bind<Service>()
.AsTransient()
builder.Register<Service>(Lifetime.Transient)
Container.Bind<Service>()
.AsCached()
builder.Register<Service>(Lifetime.Scoped)
Container.Bind<Service>()
.AsSingle()
builder.Register<Service>(Lifetime.Singleton)
Container.Bind<IService>()
.To<Service>
.AsCached()
builder.Register<IService,Service>(Lifetime.Scoped)
Container.Bind(
typeof(IInitializable),
typeof(IDisposable))
.To<Service>()
.AsCached()
builder.Register<Service>(Lifetime.Scoped)
.As<IInitializable,IDisposable>()
Container.BindInterfacesTo<Service>()
.AsCached()
builder.Register<Service>(Lifetime.Scoped)
.AsImplementedInterfaces()
Container.BindInterfacesAndSelfTo<Foo>()
.AsCached()
builder.Register<Service>(Lifetime.Scoped)
.AsImplementedInterfaces()
.AsSelf()
Container.BindInstance(obj)
builder.RegisterInstance(obj)
Container.Bind<IService>()
.FromInstance(obj)
builder.RegisterInstance<IService>(obj)
Container.Bind(
typeof(IService1),
typeof(IService2))
.FromInstance(obj)
builder.RegisterInstance(obj)
.As<IService1,IService2>()
Container.Bind(
typeof(IService1),
typeof(IService2))
.FromInstance(obj)
builder.RegisterInstance(obj)
.As<IService1,IService2>()
Container.BindInterfacesTo<Service>()
.FromInstance(obj)
builder.RegisterInstance(obj)
.AsImplementedInterfaces()
Container.BindInterfacesAndSelfTo<Service>()
.FromInstance(obj)
builder.RegisterInstance(obj)
.AsImplementedInterfaces()
.AsSelf()

Component#

ZenjectVContainer
Container.Bind<Foo>()
.FromComponentInHierarchy()
.AsCached();
builder.RegisterComponentInHierarchy<Foo>()
Container.Bind<Foo>()
.FromComponentInNewPrefab(prefab)
.AsCached()
builder.RegisterComponentInNewPrefab(prefab,
Lifetime.Scoped)
Container.Bind<Foo>()
.FromNewComponentOnNewGameObject()
.AsCached()
.WithGameObjectName("Foo1")
builder.RegisterComponentOnNewGameObject<Foo>(
Lifetime.Scoped,
"Foo1")
.UnderTransform(parentTransform)
.UnderTransform(parentTransform)
.UnderTransform(()
=>
parentTransform)
.UnderTransform(()
=>
parentTransform)

Factory#

Factory with parameter#

Zenject
public class Enemy{    readonly float speed;
    public Enemy(float speed)    {        this.speed = speed;    }
    public class Factory : PlaceholderFactory<float, Enemy>;    {    }}
Container.BindFactory<float, Enemy, Enemy.Factory>();
VContainer
public class Enemy{    readonly float speed;
    public Enemy(float speed)    {        this.speed = speed;    }}
builder.RegisterFactory<float, Enemy>(speed => new Enemy(speed));

Factory with parameter & resolve dependency at runtime#

Zenject
public class Enemy{    readonly Player player;    readonly float speed;
    public Enemy(float speed, Player player)    {        this.player = player;        this.speed = speed;    }
    public class Factory : PlaceholderFactory<float, Enemy>;    {    }}
Container.BindFactory<float, Enemy, Enemy.Factory>();
VContainer
public class Enemy{    readonly Player player;    readonly float speed;
    public Enemy(float speed, Player player)    {        this.player = player;        this.speed = speed;    }}
builder.RegisterFactory<float, Enemy>(container =>{    var player = container.Resolve<Player>();    return speed => new Enemy(speed, player);}, Lifetime.Scoped);

Factory with prefab#

Zenject
public class Enemy : MonoBehaviour{    Player player;
    [Inject]    public void Construct(Player player)    {        this.player = player;    }
    public class Factory : PlaceholderFactory<Enemy>    {    }}
Container.BindFactory<Enemy, Enemy.Factory>()    .FromComponentInNewPrefab(enemyPrefab);
VContainer
public class Enemy : MonoBehaviour{    Player player;
    public void Construct(Player player)    {        this.player = player;    }}
builder.RegisterFactory<Enemy>(container =>{    var player = container.Resolve<Player>();    return () =>    {        var enemy = Instantiate(enemyPrefab);        enemy.Construct(player);        return enemy;    };}, Lifetime.Scoped);

Misc#

ZenjectVContainer
SignalNot supported
The central messaging pattern is useful, but depends largely on the style of the project, and the preferred implementation will vary. You can choose any implementation of UniRx.MessageBroker, UniTaskPubsub, Cysharp/MessagePipe or etc.
Memory PoolNot supported
Currently, any Memory pool implementation is not embed. Please inject the implementation according to the purpose of the project into Factory etc.
Container.Bind<Foo>()
.FromComponentInNewPrefabResource("Some/Path/Foo")
Not Supported
We should load Resources using LoadAsync family. You can use RegisterInstance() etc after loading the Resource.
Container.Bind<Foo>()
.WithId("foo")
.AsCached()
Not supported
Duplicate type Resolve is not recommended. You can instead use type-specific Register builder.Register(Lifetime.Scoped).WithParameter("foo", foo)