This page aims to answer what makes Init(args) different from other dependency injection solutions for Unity.
Versatility
Init(args) improves upon just using normal serialized fields (which is also a form of dependency injection) by introducing a lot more flexibility.
Serialized fields don’t support the following use cases:
- Using services from different scenes or prefabs.
- Using interface type services.
- Resolving services dynamically at runtime (localized strings, randomized values etc.).
- Passing dependencies in code (unit tests etc.).
- Receiving services automatically.
Init(args) can do all of these things and more.

Ease-of-Use
A key aim with Init(args) has been to keep the initial learning curve as low as possible. The target has been to make creating and resolving global services at least as easy as it is with the Singleton pattern.
All it takes with Init(args) to create a global service is to add a single attribute:
[Service]
class MyManager { }
And all it takes to create a local service from a component in a scene/prefab is to pick a context menu item:

And then all it takes to have these services get automatically delivered to a component is to have the component derive from MonoBehaviour<T…> and to implement the Init method:
class Client : MonoBehaviour<MyManager, Camera>
{
protected override void Init(MyManager myManager, Camera camera)
{
Debug.Log($"{this} received {myManager} and {camera}.");
}
}
All done with only a couple of lines of code and mouse clicks!
This is in contrast to other DI frameworks that often force you to learn about concepts like installers, containers, bindings, scopes and contexts just to get started. The code needed to register services in them tends to look more like this:
class ManagerInstaller : MonoInstaller
{
public override void InstallBindings()
{
Container.Bind<MyManager>().AsSingle().NonLazy();
}
}
class CameraInstaller : MonoInstaller
{
[SerializeField] Camera camera;
public override void InstallBindings()
{
Container.Bind<Camera>().FromInstance(camera);
}
}
class Client : MonoBehaviour
{
[Inject] MyManager myManager;
[Inject] Camera camera;
void Awake() => Debug.Log($"{this} received {myManager} and {camera}.");
}
And then after this the process of configuring your hierarchy of different contexts properly can be an involved process that is easy to get wrong:

Components will also typically just silently fail to receive any services if you should forget to manually attach components that take care of injecting services to any of your prefabs. And you usually don’t get any clues about whether components depend on some services and which ones those are in the Inspector.
With Init(args) you don’t have to attach any separate injector components to your prefab assets, and you will get warnings in the Inspector and errors at runtime if any of your components have any missing dependencies:

Inspector Integration
A common pain point with DI frameworks is that it can be difficult to know exactly which services are going to be getting injected to which clients at runtime.
To avoid this, Init(args) augments the Inspector with tools that help make it easy to know which components have been registered as services, what clients those services have, and which services a particular component is going to be receiving at runtime.

With other DI frameworks you usually get no clues about which services will be delivered to a component from just looking at it in the Inspector. There could be many nested contexts spread across many scenes and prefabs, each containing several installers, and trying to figure out which services from which installers will be available for a particular client can be a difficult process.
Pure Dependency Injection
Some reflection is used in Init(args) initially when services are registered, but after that all dependency injection happens through type-safe pipelines into Init methods without any reflection being used.
In fact, it’s possible to easily initialize components with arguments using AddComponent / Instantiate directly, without even needing to involve a DI container in the process – this makes it extremely simple to write unit tests for components:
[Test]
public void Client_Receives_Service()
{
var gameObject = new GameObject();
var service = new Service();
var client = gameObject.AddComponent<Client, Service>(service); // <- inject dependencies!
Assert.That(client.Service, Is.EqualTo(service));
}
Most other DI frameworks inject services to members marked with an [Inject] attribute, which typically relies on reflection being used, or some sort of reflection baking process being performed during every build or script compilation. In addition to degraded performance, or increased build/compilation times, this also typically means that it’s impossible to pass services to clients without involving a service container in the process.
With Init(args) it’s always easy to pass whatever services you want to all your components when you initialize them in code, or to drag-and-drop whatever service you want to them using the Inspector when you configure them in Edit Mode. This means that, unlike most DI frameworks, Init(args) is very designer friendly; you don’t need to know how to write any code in order to swap a service that should be delivered to a client with a different one.

Performance
Because Init(args) doesn’t rely on any reflection being used when resolving dependencies it has top-tier performance.
Below is a chart comparing how fast different dependency injection solutions can handle automatically resolving six dependencies of a component when it is being instantiated:
Instantiate x 1,000,000
| Tested | Total (s) | Average (ns) | Details |
| Baseline | 1.664 | 1.664 | No injection |
| [SerializeField] | 1.904 | 1.904 | |
| Init(args) | 3.053 | 3.053 | via MonoBehaviour<T> |
| VContainer | 3.343 | 3.343 | via InjectGameObject, Source Generator: On |
| Reflex | 3.757 | 3.757 | via GameObjectSelfInjector |
| [SerializeReference] | 8.312 | 8.312 | |
| Extenject | 8.718 | 8.718 | via ZenAutoInjecter, Reflection Baking: Off |
| USyrup | 59.359 | 59.359 | via SyrupInjector |
Lightweight
Init(args) doesn’t have any dependencies to third-party packages and is very lightweight. While frameworks like Zenject and VContainer can add something like 7 MB to your build size, Init(args) comes in at an order of magnitude smaller at ~700KB.
Init(args) also doesn’t need a reflection baking step before each build, or an IL post-processing step during every script compilation to avoid reflection being used, so you don’t have to worry about them impacting your build times or script compilation times.
The codebase of Init(args) also consists of hundreds of fewer classes than those of Zenject and VContainer, which can help contribute to faster import times.