Comparing to Zenject
Unityで動作する他のDIライブラリに Zenject があります。 Zenject と比較すると、VContainerは以下のような特徴があります。
- 良好なパフォーマンス
- リフレクションやアサーションなどはコンテナをビルドするフェイズですべて完了するため、オブジェクト数の増加につれてのパフォーマンス劣化が小さい。
- Zenject はシーン開始時に全ての GameObject を リフレクションで走査するなど、暗黙のうちに高コストな処理が実行されることがある。VContainerはより明示的にこのようなことを指定する。
- コードが短く実装がより読みやすくなっている
- 機能を厳選している。また、ありとあらゆるオブジェクトをDIコンテナで管理することは推奨していない。
- Zenjectでは、データとして振る舞うオブジェクトや、動的な寿命を持つViewコンポーネントを直接DIする使い方ができるようになっている。これはDIの設定が過度に複雑になっていくという欠点がある。
- VContainerでは、MonoBehaviour へ Injectするよりは MonoBehaviour を Injectすることをどちらかというと推奨している。
Code size (v1.3.0)
API difference
Basic
Zenject | VContainer |
---|---|
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
Zenject | VContainer |
---|---|
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
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>();
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
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>();
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
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);
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
Zenject | VContainer |
---|---|
Signal | Not 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 Pool | Not 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) |