Details
- Introduction
- Features
- Installation *
- History
- Documentation
- What is Dependency Injection?
- Introduction to Zenject API
- Advanced
- Binding
- Scriptable Object Installer
- Runtime Parameters For Installers
- Composite Installers
- Using Zenject Outside Unity Or For DLLs
- Zenject Settings
- Signals
- Factories: Creating Objects Dynamically
- Memory Pools
- Update / Initialization Order
- Zenject Order Of Operations
- Injecting data across scenes
- Scene Parenting Using Contract Names
- Default Scene Parents
- ZenAutoInjecter
- Scene Decorators
- Sub-Containers And Facades
- Writing Automated Unit Tests / Integration Tests
- Philosophy Of Zenject
- Just-In-Time Resolving Using LazyInject<>
- Open Generic Types
- Notes About Destruction/Dispose Order
- UniRx Integration
- Auto-Mocking using Moq
- Creating Unity EditorWindow's with Zenject
- Optimization Recommendations/Notes
- Reflection Baking
- Upgrade Guide for Zenject 6
- DiContainer Methods
- Frequently Asked Questions
- Isn't this overkill? I mean, is using statically accessible singletons really that bad?
- Is there a way to integrate with the upcoming Unity ECS?
- Does this work on AOT platforms such as iOS and WebGL?
- How is performance?
- Does Zenject support multithreading?
- How do I use Unity style Coroutines in normal C# classes?
- Are there any more sample projects to look at?
- What games/applications/libraries are using Zenject?
- I keep getting errors complaining about circular reference! How to address this?
- Cheat Sheet
- Further Help
- Release Notes
- License
Note that if you are looking for the older documentation for Zenject you can find that here: Zenject 3.x, Zenject 4.x and Zenject 5.x
Zenject is a lightweight highly performant dependency injection framework built specifically to target Unity 3D (however it can be used outside of Unity as well). It can be used to turn your application into a collection of loosely coupled parts with highly segmented responsibilities. Zenject can then glue the parts together in many different configurations to allow you to easily write, re-use, refactor and test your code in a scalable and extremely flexible way.
Tested in Unity 3D on the following platforms:
- PC/Mac/Linux
- iOS
- Android
- WebGL
- PS4 (with IL2CPP backend)
- Windows Store (including 8.1, Phone 8.1, Universal 8.1 and Universal 10 - both .NET and IL2CPP backend)
IL2CPP is supported, however there are some gotchas - see here for details
This project is open source.
For general troubleshooting / support, please post to stack overflow using the tag 'zenject', or post in the zenject google group
Or, if you have found a bug, you are also welcome to create an issue on the [github page]https://github.com/modesttree/Zenject), or a pull request if you have a fix / extension. There is also a gitter chat that you can join for real time discussion.
- Injection
- Supports both normal C# classes and MonoBehaviours
- Constructor injection
- Field injection
- Property injection
- Method injection
- Conditional binding (eg. by type, by name, etc.)
- Optional dependencies
- Support for creating objects after initialization using factories
- Nested Containers aka Sub-Containers
- Injection across different Unity scenes to pass information from one scene to the next
- Scene parenting, to allow one scene to inherit the bindings from another
- Support for global, project-wide bindings to add dependencies for all scenes
- Convention based binding, based on class name, namespace, or any other criteria
- Ability to validate object graphs at editor time (including dynamic object graphs created via factories)
- Automatic binding on components in the scene using the
ZenjectBinding
component - Auto-Mocking using the Moq library
- Built-in support for memory pools
- Support for decorator pattern using decorator bindings
- Support for automatically mapping open generic types
- Built in support for unit test, integration tests, and scene tests
- Just-in-time injection using the LazyInject<> construct
- Support for multiple threads for resolving/instantiating
- Support for 'reflection baking' to eliminate costly reflection operations completely by directly modifying the generated assemblies
- Automatic injection of game objects using ZenAutoInjecter component
You can install Zenject using any of the following methods
-
From Releases Page Here you can choose between the following:
- Zenject-WithAsteroidsDemo.vX.X.unitypackage - This is equivalent to what you find in the Asset Store and contains both sample games "Asteroids" and "SpaceFighter" as part of the package. All the source code for Zenject is included here.
- Zenject.vX.X.unitypackage - Same as above except without the Sample projects.
- Zenject-NonUnity.vX.X.zip - Use this if you want to use Zenject outside of Unity (eg. just as a normal C# project)
-
From the Unity Asset Store
- Normally this should be the same as what you find in the Releases section, but may also be slightly out of date since Unity Asset Store can take a week or so to review submissions sometimes.
-
UPM Branch
- This option is a feature request. The package will be released when Unity is ready. Unity is not giving any insights on the development status. But the expectation is in the first or second release of 2020.
- If you can not wait. There is an alternative. But you will need the Unity extension found here. And the package found here.
-
From Source
- After syncing the git repo, note that you will have to build the
Zenject-Usage.dll
by building the solution atAssemblyBuild\Zenject-usage\Zenject-usage.sln
. Or, if you prefer you can getZenject-Usage.dll
from Releases section instead - Then you can copy the
UnityProject/Assets/Plugins/Zenject
directory to your own Unity3D project.
- After syncing the git repo, note that you will have to build the
Note that when importing Zenject into your unity project, you can uncheck any folder underneath the OptionalExtras folder for cases where you don't want to include it, or if you just want the core zenject functionality, you can uncheck the entire OptionalExtras directory.
Unity is a fantastic game engine, however the approach that new developers are encouraged to take does not lend itself well to writing large, flexible, or scalable code bases. In particular, the default way that Unity manages dependencies between different game components can often be awkward and error prone.
This project was started because at the time there wasn't any DI frameworks for Unity, and having used DI frameworks outside of Unity (eg. Ninject) and seeing the benefits, I felt it was important to remedy that.
Finally, I will just say that if you don't have experience with DI frameworks, and are writing object oriented code, then trust me, you will thank me later! Once you learn how to write properly loosely coupled code using DI, there is simply no going back.
The Zenject documentation is split up into the following sections. It is split up into two parts (Introduction and Advanced) so that you can get up and running as quickly as possible. I would recommend at least skimming through the Introduction section before beginning, but then feel free to jump around in the advanced section as necessary
Another great starting point is to watch this youtube series on zenject created by Infallible Code.
You might also benefit from playing with the provided sample projects (which you can find by opening Zenject/OptionalExtras/SampleGame1
or Zenject/OptionalExtras/SampleGame2
).
If you are a DI veteran, then it might be worth taking a look at the cheatsheet at the bottom of this page, which should give you an idea of the syntax, which might be all you need to get started.
The tests may also be helpful to show usage for each specific feature (which you can find at Zenject/OptionalExtras/UnitTests
and Zenject/OptionalExtras/IntegrationTests
)
Also see further reading section for some external zenject tutorials provided elsewhere.
What follows is a general overview of Dependency Injection from my perspective. However, it is kept light, so I highly recommend seeking other resources for more information on the subject, as there are many other people (often with better writing ability) that have written about the theory behind it.
When writing an individual class to achieve some functionality, it will likely need to interact with other classes in the system to achieve its goals. One way to do this is to have the class itself create its dependencies, by calling concrete constructors:
public class Foo
{
ISomeService _service;
public Foo()
{
_service = new SomeService();
}
public void DoSomething()
{
_service.PerformTask();
…
}
}
This works fine for small projects, but as your project grows it starts to get unwieldy. The class Foo is tightly coupled to class 'SomeService'. If we decide later that we want to use a different concrete implementation then we have to go back into the Foo class to change it.
After thinking about this, often you come to the realization that ultimately, Foo shouldn't bother itself with the details of choosing the specific implementation of the service. All Foo should care about is fulfilling its own specific responsibilities. As long as the service fulfills the abstract interface required by Foo, Foo is happy. Our class then becomes:
public class Foo
{
ISomeService _service;
public Foo(ISomeService service)
{
_service = service;
}
public void DoSomething()
{
_service.PerformTask();
...
}
}
This is better, but now whatever class is creating Foo (let's call it Bar) has the problem of filling in Foo's extra dependencies:
public class Bar
{
public void DoSomething()
{
var foo = new Foo(new SomeService());
foo.DoSomething();
...
}
}
And class Bar probably also doesn't really care about what specific implementation of SomeService Foo uses. Therefore we push the dependency up again:
public class Bar
{
ISomeService _service;
public Bar(ISomeService service)
{
_service = service;
}
public void DoSomething()
{
var foo = new Foo(_service);
foo.DoSomething();
...
}
}
So we find that it is useful to push the responsibility of deciding which specific implementations of which classes to use further and further up in the 'object graph' of the application. Taking this to an extreme, we arrive at the entry point of the application, at which point all dependencies must be satisfied before things start. The dependency injection lingo for this part of the application is called the 'composition root'. It would normally look like this:
var service = new SomeService();
var foo = new Foo(service);
var bar = new Bar(service);
var qux = new Qux(bar);
.. etc.
DI frameworks such as Zenject simply help automate this process of creating and handing out all these concrete dependencies, so that you don't need to explicitly do so yourself like in the above code.
There are many misconceptions about DI, due to the fact that it can be tricky to fully wrap your head around at first. It will take time and experience before it fully 'clicks'.
As shown in the above example, DI can be used to easily swap different implementations of a given interface (in the example this was ISomeService). However, this is only one of many benefits that DI offers.
More important than that is the fact that using a dependency injection framework like Zenject allows you to more easily follow the 'Single Responsibility Principle'. By letting Zenject worry about wiring up the classes, the classes themselves can just focus on fulfilling their specific responsibilities.
Another common mistake that people new to DI make is that they extract interfaces from every class, and use those interfaces everywhere instead of using the class directly. The goal is to make code more loosely coupled, so it's reasonable to think that being bound to an interface is better than being bound to a concrete class. However, in most cases the various responsibilities of an application have single, specific classes implementing them, so using interfaces in these cases just adds unnecessary maintenance overhead. Also, concrete classes already have an interface defined by their public members. A good rule of thumb instead is to only create interfaces when the class has more than one implementation or in cases where you intend to have multiple implemenations in the future (this is known, by the way, as the Reused Abstraction Principle)
Other benefits include:
- Refactorability - When code is loosely coupled, as is the case when using DI properly, the entire code base is much more resilient to changes. You can completely change parts of the code base without having those changes wreak havoc on other parts.
- Encourages modular code - When using a DI framework you will naturally follow better design practices, because it forces you to think about the interfaces between classes.
- Testability - Writing automated unit tests or user-driven tests becomes very easy, because it is just a matter of writing a different 'composition root' which wires up the dependencies in a different way. Want to only test one subsystem? Simply create a new composition root. Zenject also has some support for avoiding code duplication in the composition root itself (using Installers - described below).
Also see here and here for further discussion and justification for using a DI framework.
using Zenject;
using UnityEngine;
using System.Collections;
public class TestInstaller : MonoInstaller
{
public override void InstallBindings()
{
Container.Bind<string>().FromInstance("Hello World!");
Container.Bind<Greeter>().AsSingle().NonLazy();
}
}
public class Greeter
{
public Greeter(string message)
{
Debug.Log(message);
}
}
You can run this example by doing the following:
- Create a new scene in Unity
- Right Click inside the Hierarchy tab and select
Zenject -> Scene Context
- Right Click in a folder within the Project Tab and Choose
Create -> Zenject -> MonoInstaller
. Name it TestInstaller.cs - Add your TestInstaller script to the scene (as its own GameObject or on the same GameObject as the SceneContext, it doesn't matter)
- Add a reference to your TestInstaller to the properties of the SceneContext by adding a new row in the inspector of the "Installers" property (press the + button) and then dragging TestInstaller to it
- Open up TestInstaller and paste the above code into it
- Validate your scene by either selecting Edit -> Zenject -> Validate Current Scene or hitting CTRL+ALT+V. (note that this step isn't necessary but good practice to get into)
- Run
- Note also, that you can use the shortcut
CTRL+SHIFT+R
to "validate then run". Validation is usually fast enough that this does not add a noticeable overhead to running your game, and when it does detect errors it is much faster to iterate on since you avoid the startup time. - Observe unity console for output
The SceneContext MonoBehaviour is the entry point of the application, where Zenject sets up all the various dependencies before kicking off your scene. To add content to your Zenject scene, you need to write what is referred to in Zenject as an 'Installer', which declares all the dependencies and their relationships with each other. All dependencies that are marked as "NonLazy" are automatically created after the installers are run, which is why the Greeter class that we added above gets created on startup. If this doesn't make sense to you yet, keep reading!
There are many different ways of declaring dependencies on the container, which are documented in the next section. There are also several ways of having these dependencies injected into your classes. These are:
1 - Constructor Injection
public class Foo
{
IBar _bar;
public Foo(IBar bar)
{
_bar = bar;
}
}
2 - Field Injection
public class Foo
{
[Inject]
IBar _bar;
}
Field injection occurs immediately after the constructor is called. All fields that are marked with the [Inject]
attribute are looked up in the container and given a value. Note that these fields can be private or public and injection will still occur.
3 - Property Injection
public class Foo
{
[Inject]
public IBar Bar
{
get;
private set;
}
}
Property injection works the same as field injection except is applied to C# properties. Just like fields, the setter can be private or public in this case.
4 - Method Injection
public class Foo
{
IBar _bar;
Qux _qux;
[Inject]
public void Init(IBar bar, Qux qux)
{
_bar = bar;
_qux = qux;
}
}
Method Inject injection works very similarly to constructor injection.
Note the following:
- Inject methods are the recommended approach for MonoBehaviours, since MonoBehaviours cannot have constructors.
- There can be any number of inject methods. In this case, they are called in the order of Base class to Derived class. This can be useful to avoid the need to forward many dependencies from derived classes to the base class via constructor parameters, while also guaranteeing that the base class inject methods complete first, just like how constructors work.
- Inject methods are called after all other injection types. It is designed this way so that these methods can be used to execute initialization logic which might make use of injected fields or properties. Note also that you can leave the parameter list empty if you just want to do some initialization logic only.
- You can safely assume that the dependencies that you receive via inject methods will themselves already have been injected (the only exception to this is in the case where you have circular dependencies). This can be important if you use inject methods to perform some basic initialization, since in that case you may need the given dependencies to be initialized as well
- Note however that it is usually not a good idea to use inject methods for initialization logic. Often it is better to use IInitializable.Initialize or Start() methods instead, since this will allow the entire initial object graph to be created first.
Recommendations
Best practice is to prefer constructor/method injection compared to field/property injection.
- Constructor injection forces the dependency to only be resolved once, at class creation, which is usually what you want. In most cases you don't want to expose a public property for your initial dependencies because this suggests that it's open to changing.
- Constructor injection guarantees no circular dependencies between classes, which is generally a bad thing to do. Zenject does allow circular dependencies when using other injections types however such as method/field/property injection
- Constructor/Method injection is more portable for cases where you decide to re-use the code without a DI framework such as Zenject. You can do the same with public properties but it's more error prone (it's easier to forget to initialize one field and leave the object in an invalid state)
- Finally, Constructor/Method injection makes it clear what all the dependencies of a class are when another programmer is reading the code. They can simply look at the parameter list of the method. This is also good because it will be more obvious when a class has too many dependencies and should therefore be split up (since its constructor parameter list will start to seem long)
The core of a dependency injection framework is the DI container. In it's simplest form it's an object which contains a dictionary that holds all the registrations. In this section we are going to cover the 'register a new mapping' part. In Zenject it's called binding. As it creates a binding between an abstraction to a concrete type.
Every dependency injection framework is ultimately just a framework to bind types to instances.
In Zenject, dependency mapping is done by adding bindings to something called a container. The container should then 'know' how to create all the object instances in your application, by recursively resolving all dependencies for a given object.
When the container is asked to construct an instance of a given type, it uses C# reflection to find the list of constructor arguments, and all fields/properties that are marked with an [Inject] attribute. It then attempts to resolve each of these required dependencies, which it uses to call the constructor and create the new instance.
Each Zenject application therefore must tell the container how to resolve each of these dependencies, which is done via Bind commands. For example, given the following class:
public class Foo
{
IBar _bar;
public Foo(IBar bar)
{
_bar = bar;
}
}
You can wire up the dependencies for this class with the following:
Container.Bind<Foo>().AsSingle();
Container.Bind<IBar>().To<Bar>().AsSingle();
This tells Zenject that every class that requires a dependency of type Foo should use the same instance, which it will automatically create when needed. And similarly, any class that requires the IBar interface (like Foo) will be given the same instance of type Bar.
The full format for the bind command is the following. Note that in most cases you will not use all of these methods and that they all have logical defaults when unspecified
Container.Bind<ContractType>() .WithId(Identifier) .To<ResultType>() .FromConstructionMethod() .AsScope() .WithArguments(Arguments) .OnInstantiated(InstantiatedCallback) .When(Condition) .(Copy|Move)Into(All|Direct)SubContainers() .NonLazy() .IfNotBound();
Where:
-
ContractType = The type that you are creating a binding for.
- This value will correspond to the type of the field/parameter that is being injected.
-
ResultType = The type to bind to.
- Default: ContractType
- This type must either to equal to ContractType or derive from ContractType. If unspecified, it assumes ToSelf(), which means that the ResultType will be the same as the ContractType. This value will be used by whatever is given as the ConstructionMethod to retrieve an instance of this type
-
Identifier = The value to use to uniquely identify the binding. This can be ignored in most cases, but can be quite useful in cases where you need to distinguish between multiple bindings with the same contract type. See here for details.
-
ConstructionMethod = The method by which an instance of ResultType is created/retrieved. See this section for more details on the various construction methods.
- Default: FromNew()
- Examples: eg. FromGetter, FromMethod, FromResolve, FromComponentInNewPrefab, FromSubContainerResolve, FromInstance, etc.
-
Scope = This value determines how often (or if at all) the generated instance is re-used across multiple injections.
-
Default: AsTransient. Note however that not all bindings have a default, so an exception will be thrown if not supplied. The bindings that do not require the scope to be set explicitly are any binding with a construction method that is a search rather than creating a new object from scratch (eg. FromMethod, FromComponentX, FromResolve, etc.)
-
It can be one of the following:
- AsTransient - Will not re-use the instance at all. Every time ContractType is requested, the DiContainer will execute the given construction method again
- AsCached - Will re-use the same instance of ResultType every time ContractType is requested, which it will lazily generate upon first use
- AsSingle - Exactly the same as AsCached, except that it will sometimes throw exceptions if there already exists a binding for ResultType. It is simply a way to ensure that the given ResultType is unique within the container. Note however that it will only guarantee that there is only one instance across the given container, which means that using AsSingle with the same binding in a sub-container could generate a second instance.
-
In most cases, you will likely want to just use AsSingle, however AsTransient and AsCached have their uses too.
-
-
Arguments = A list of objects to use when constructing the new instance of type ResultType. This can be useful as an alternative to adding other bindings for the arguments in the form
Container.BindInstance(arg).WhenInjectedInto<ResultType>()
-
InstantiatedCallback = In some cases it is useful to be able customize an object after it is instantiated. In particular, if using a third party library, it might be necessary to change a few fields on one of its types. For these cases you can pass a method to OnInstantiated that can customize the newly created instance. For example:
Container.Bind<Foo>().AsSingle().OnInstantiated<Foo>(OnFooInstantiated); void OnFooInstantiated(InjectContext context, Foo foo) { foo.Qux = "asdf"; }
Or, equivalently:
Container.Bind<Foo>().AsSingle().OnInstantiated<Foo>((ctx, foo) => foo.Bar = "qux");
Note that you can also bind a custom factory using FromFactory that directly calls Container.InstantiateX before customizing it for the same effect, but OnInstantiated can be easier in some cases
-
Condition = The condition that must be true for this binding to be chosen. See here for more details.
-
(Copy|Move)Into(All|Direct)SubContainers = This value can be ignored for 99% of users. It can be used to automatically have the binding inherited by subcontainers. For example, if you have a class Foo and you want a unique instance of Foo to be automatically placed in the container and every subcontainer, then you could add the following binding:
Container.Bind<Foo>().AsSingle().CopyIntoAllSubContainers()
In other words, the result will be equivalent to copying and pasting the
Container.Bind<Foo>().AsSingle()
statement into the installer for every sub-container.Or, if you only wanted Foo in the subcontainers and not the current container:
Container.Bind<Foo>().AsSingle().MoveIntoAllSubContainers()
Or, if you only wanted Foo to be in the immediate child subcontainer, and not the subcontainers of these subcontainers:
Container.Bind<Foo>().AsSingle().MoveIntoDirectSubContainers()
-
NonLazy = Normally, the ResultType is only ever instantiated when the binding is first used (aka "lazily"). However, when NonLazy is used, ResultType will immediately be created on startup.
-
IfNotBound = When this is added to a binding and there is already a binding with the given contract type + identifier, then this binding will be skipped.
-
FromNew - Create via the C# new operator. This is the default if no construction method is specified.
// These are both the same Container.Bind<Foo>(); Container.Bind<Foo>().FromNew();
-
FromInstance - Add a given instance to the container. Note that the given instance will not be injected in this case. If you also want your instance to be injected at startup, see QueueForInject
Container.Bind<Foo>().FromInstance(new Foo()); // You can also use this short hand which just takes ContractType from the parameter type Container.BindInstance(new Foo()); // This is also what you would typically use for primitive types Container.BindInstance(5.13f); Container.BindInstance("foo"); // Or, if you have many instances, you can use BindInstances Container.BindInstances(5.13f, "foo", new Foo());
-
FromMethod - Create via a custom method
Container.Bind<Foo>().FromMethod(SomeMethod); Foo SomeMethod(InjectContext context) { ... return new Foo(); }
-
FromMethodMultiple - Same as FromMethod except allows returning multiple instances at once (or zero).
Container.Bind<Foo>().FromMethodMultiple(GetFoos); IEnumerable<Foo> GetFoos(InjectContext context) {