Blazing fast, minimal but complete dependency injection library for Unity
Reflex is an Dependency Injection framework for Unity. Making your classes independent of its dependencies, granting better separation of concerns. It achieves that by decoupling the usage of an object from its creation. This helps you to follow SOLIDβs dependency inversion and single responsibility principles. Making your project more readable, testable and scalable.
π Table Of Contents
- Fast: up to 414% faster than VContainer, up to 800% faster than Zenject.
- GC Friendly: up to 28% less allocations than VContainer, up to 921% less allocations than Zenject.
- AOT Support: Basically there's no runtime Emit, so it works fine on IL2CPP builds. [*]
- Contract Table: Allows usages of APIs like
container.All<IDisposable> - Immutable Container: Performant thread safety free of lock plus predictable behavior.
- Source Generated: Implements modern Roslyn source generator to minimize reflection usage and speed up injection.
Compatible with the following platforms:
- iOS
- Android
- Windows/Mac/Linux
- PS4/PS5
- Xbox One/S/X and Xbox Series X/S
- WebGL
You can install Reflex using any of the following methods:
https://github.com/gustavopsantos/reflex.git?path=/Assets/Reflex/#14.0.0
- In Unity, open Window β Package Manager.
- Press the + button, choose "Add package from git URL..."
- Enter url above and press Add.
openupm install com.gustavopsantos.reflex- Download the .unitypackage from releases page.
- Import Reflex.X.X.X.unitypackage
- Install Reflex
- Create
RootInstaller.cswith
using Reflex.Core;
using UnityEngine;
public class RootInstaller : MonoBehaviour, IInstaller
{
public void InstallBindings(ContainerBuilder builder)
{
builder.RegisterValue("Hello"); // Note that values are always registered as singletons
}
}- In unity project window
- Right click over any folder, Create β Reflex β RootScope. Since RootScope is strongly referenced by ReflexSettings, you can create it anywhere, it does not need to be inside
Resourcesfolder. - Select
RootScopeyou just created - Add
RootInstaller.csas a component - Create directory
Assets/Resources - Right click over
Resourcesfolder, Create β Reflex β Settings. ReflexSettings should always be created directly insideResourcesfolder, without any subfolder. - Select
ReflexSettingsScriptableObject and add theRootScopeprefab to the RootScopes list - Create new scene
Greet - Add
GreettoBuild SettingsβScenes In Build - Create
Greeter.cswith
using UnityEngine;
using System.Collections.Generic;
using Reflex.Attributes;
public class Greeter : MonoBehaviour
{
[Inject] private readonly IEnumerable<string> _strings;
private void Start()
{
Debug.Log(string.Join(" ", _strings));
}
}- Add
Greeter.csto any gameobject inGreetscene - Inside Greet scene, create a scene scope, Right Click on Hierarchy > Reflex > SceneScope.
- Create
GreetInstaller.cswith
using Reflex.Core;
using UnityEngine;
public class GreetInstaller : MonoBehaviour, IInstaller
{
public void InstallBindings(ContainerBuilder builder)
{
builder.RegisterValue("World"); // Note that values are always registered as singletons
}
}- Add
GreetInstaller.csto theSceneScopeyou created on step 14 - Create new scene
Boot - Add
BoottoBuild SettingsβScenes In Build - Create
Loader.cswith
using Reflex.Core;
using UnityEngine;
public class Loader : MonoBehaviour
{
private void Start()
{
void InstallExtra(UnityEngine.SceneManagement.Scene scene, ContainerBuilder builder)
{
builder.RegisterValue("of Developers");
}
// This way you can access ContainerBuilder of the scene that is currently building
ContainerScope.OnSceneContainerBuilding += InstallExtra;
// If you are loading scenes without addressables
UnityEngine.SceneManagement.SceneManager.LoadSceneAsync("Greet").completed += operation =>
{
ContainerScope.OnSceneContainerBuilding -= InstallExtra;
};
// If you are loading scenes with addressables
UnityEngine.AddressableAssets.Addressables.LoadSceneAsync("Greet").Completed += operation =>
{
ContainerScope.OnSceneContainerBuilding -= InstallExtra;
};
}
}- Add
Loader.csto any gameobject atBootscene - Thats it, hit play from
Bootscene - When Greet scene is loaded, there should be 3 instances implementing string contract
- So when Greeter.Start is called, you should see the following log in the unity console:
Hello World of Developers
As of version 8.0.0 Reflex has stopped automatically managing dependency injection for any scene.
If you plan on using dependency injection in one of your scenes, add a game object somewhere in the hierarchy with a ContainerScope component attached. You can still manage shared dependencies using Root Container or utilize this Scene Containers for limited access. This component must be present at scene load time.
This allows users to consume injected dependencies on callbacks such as Awake and OnEnable while giving more granular control over which scene should be injected or not.
Reflex's default strategy for creating containers involves initially generating a root container. For each newly loaded scene, an additional container is created, which always inherits from the root container. This container hierarchy mirrors the flat hierarchy of Unity scenes. You can see how the structure looks like below:
graph
RootContainer --> BootScene
RootContainer --> LobbyScene
RootContainer --> GameScene
RootContainer --> GameModeTwoScene
To do this or whatever else you want with scene ContainerBuilder you can access it with SceneScope.OnSceneContainerBuilding like we show in Loader.cs in "Getting Started" section.
// here we take boot scene container just for an example, you can use any container you need
var bootSceneContainer = gameObject.scene.GetSceneContainer();
void OverrideParent(Scene scene, ContainerBuilder builder) => builder.SetParent(bootSceneContainer);
ContainerScope.OnSceneContainerBuilding += OverrideParent;
// If you are loading scenes without addressables
SceneManager.LoadSceneAsync("Lobby", LoadSceneMode.Additive).completed += operation =>
{
ContainerScope.OnSceneContainerBuilding -= OverrideParent;
};
// If you are loading scenes with addressables
Addressables.LoadSceneAsync("Lobby", LoadSceneMode.Additive).Completed += operation =>
{
ContainerScope.OnSceneContainerBuilding -= OverrideParent;
};By utilizing this API, you can create hierarchical structures such as the one shown below:
graph
RootContainer-->BootScene
BootScene-->LobbyScene
- Please note that it is not possible to override the parent container for the initial scene loaded by Unity.
- Exercise caution when managing the scene lifecycle with this type of hierarchy. For example, unloading a parent scene before its child scenes can lead to unexpected behavior, as the parent container will be disposed while the child scenes are still active. As a general rule, always unload the scene hierarchy from the bottom up, starting with the child scenes and progressing to the parent scenes.
Container scoping refers to the ability of being able to create a container inheriting the registrations of its parent container while also being able to extend it.
The root scope by default share all its dependencies with all SceneScopes.
It is created lazily once the first scene containing a ContainerScope is loaded.
To register bindings to it, create a prefab, name it how you wish, the name is not used as a identifier, and attach a "ContainerScope" component to it.
Select ReflexSettings and add the prefab you created to the RootScopes list.
Then, create your installer as MonoBehaviour and implement IInstaller interface.
Remember to attach your installer to the RootScope prefab, as RootScope searches for every child implementing IInstaller when it's time to create the Root Container.
There's a menu item to ease the process: Assets > Create > Reflex > RootScope
You can create multiple RootScope prefabs, and when its time to create the root container, all active RootScope prefabs will be merged, this allow a better separation of concerns if required.
Note that RootScope prefab is not required, so if ReflexSettings.RootScopes list is empty, an empty root container will be created.
RootContainer instance will be disposed once app closes/app quits.
Invokes ContainerScope.OnRootContainerBuilding static event while being built in case you need to dynamically extend it, you can use [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] method attribute to register to this event.
Note that unity does not call OnDestroy deterministically, so rule of thumb is do not rely on injected dependencies on OnDestroy event functions.
It is scoped from RootScope, inheriting all its bindings.
It is created and injected on Awake, using a custom execution order to execute as the earliest Awake, see ContainerScope.SceneContainerScopeExecutionOrder.
To register bindings to it, create a gameobject on desired scene, name it "SceneScope", put it as root game object, and attach a "ContainerScope" component to it.
Then, create your installer as MonoBehaviour and implement IInstaller interface.
Remember to attach your installer to your SceneScope gameobject, as SceneScope searches for every child implementing IInstaller when it's time to create the SceneScope container.
There's a menu item to ease the process: GameObject > Reflex > SceneScope
Remember to have a single SceneScope to avoid undesired behaviour.
Note that SceneScope gameobject is required only if you want its scene to be injected, in case Reflex do not find SceneScope, the scene injection will be skipped for that specific scene missing SceneScope.
SceneScope instance will be disposed once scene is unloaded.
Invokes ContainerScope.OnSceneContainerBuilding static event while being built for every scene containing a SceneScope in case you need to dynamically extend it,
Note that unity does not call OnDestroy deterministically, so rule of thumb is do not rely on injected dependencies on OnDestroy event functions.
using var childContainer = parentContainer.Scope(builder =>
{
// Extend your scoped container by adding extra registrations here
});Used to register user-created objects into the container.
- The instance is created outside the container
- The container **does not construct this object
- Gets disposed by the container if it implements
IDisposable - The registered instance always behaves as a singleton
Registers a type that the container is responsible for creating.
- The type must be constructible by the container
- The container resolves constructor dependencies automatically
- The container resolves decorated fields, properties and methods automatically
- Instances are created according to the configured resolution
- Instances are provided according to the configured lifetime
Allows the user to provide a factory function that the container will invoke to create instances.
- Full control over object creation
- Can depend on runtime data
- Factory receives the resolution container as context for further resolutions if needed
- Calls to the registered function happens according to the configured resolution
- Calls to the registered function happens according to the configured lifetime
The Resolution enum defines when a dependency is instantiated.
- Instance is created only when first resolved
- Default behavior in most DI frameworks
- Improves startup performance
- Instance is created during container build
- Useful for early validation
- Ideal for critical systems required at startup
The Lifetime enum defines how instances will be provided.
- One instance shared globally
- Same object returned for every resolve
- One instance per container
- Each container has their own unique instance
- A new instance per resolve
- No instance sharing
| Lifetime | Instance Behavior |
|---|---|
| Singleton | One global instance |
| Scoped | One per container |
| Transient | One per resolve |
If your type is non-mono, and it's going to be created by the container, then the most recommended way to inject dependencies into it its by constructor injection. It's simply as just requesting the contracts you need as following example:
private class Foo
{
...
public Foo(IInputManager inputManager, IEnumerable<IManager> managers)
{
...
}
}Note that constructor injection relies on
Resolve<TContract>API, so in case there's two objects withIInputManagercontract, the last one will be injected.
Attribute injection is the way to go for MonoBehaviours. You can use it to inject fields, writeable properties and methods like following:
class Foo : MonoBehaviour
{
[Inject] private readonly IInputManager _inputManager;
[Inject] public IEnumerable<IManager> Managers { get; private set; }
[Inject]
private void Inject(IEnumerable<int> numbers) // Method name here does not matter
{
...
}
}Note that attribute injection also works on non-mono classes.
Container::Single<TContract> actually validates that there's a single binding implementing given contract, and returns it.
If there's more than one the following exception will be thrown.
InvalidOperationException: Sequence contains more than one element
It's recommended for every binding that you know that there should be a single binding implementing the contract.
Container::Resolve<TContract> runs no validations, and return the last valid object implementing given contract.
Container::All<TContract> returns all objects implementing given contract.
Example:
private void Documentation_Bindings()
{
var container = new ContainerBuilder()
.RegisterValue(1)
.RegisterValue(2)
.RegisterValue(3)
.Build();
Debug.Log(string.Join(", ", container.All<int>())); // Prints: 1, 2, 3
}Selective Resolution is the technique of resolving a specific dependency or implementation by using a composite key (often a combination of a string identifier and a type). This approach allows developers to choose exactly which binding to use in scenarios where multiple bindings of the same type are registered.
Reflex does not support selective resolution natively, there are no WithId builder methods or [Inject(Id = "FooId")] attributes like in some other DI frameworks. However there is a simple and type-safe alternative: instead of registering multiple contracts for the same type (e.g., two string registrations), you can create unique wrapper types to distinguish them.
Below is an example demonstrating this approach:
using NUnit.Framework;
using Reflex.Core;
using UnityEngine;
namespace Reflex.EditModeTests
{
public class TypedInstance<T>
{
private readonly T _value;
protected TypedInstance(T value) => _value = value;
public static implicit operator T(TypedInstance<T> typedInstance) => typedInstance._value;
}
public class AppName : TypedInstance<string>
{
public AppName(string value): base(value) {}
}
public class AppVersion : TypedInstance<string>
{
public AppVersion(string value): base(value) {}
}
public class AppWindow
{
private readonly string _appName;
private readonly string _appVersion;
public AppWindow(AppName appName, AppVersion appVersion)
{
_appName = appName;
_appVersion = appVersion;
}
public void Present() => Debug.Log($"Hello from {_appName} version: {_appVersion}");
}
public class SelectiveBindingTests
{
[Test]
public void TestSelectiveBinding()
{
var container = new ContainerBuilder()
.RegisterValue(typeof (AppWindow))
.RegisterValue(new AppVersion("0.9"))
.RegisterValue(new AppName("MyHelloWorldConsoleApp"))
.Build();
var appWindow = container.Resolve<AppWindow>();
appWindow.Present();
}
}
}OnContainerBuilt is a instance callback of ContainerBuilder, it is called once the container is fully built and initialized properly.
Should be used to inject fields, writeable properties and methods like following:
class Foo : MonoBehaviour
{
[Inject] private readonly IInputManager _inputManager;
[Inject] public IEnumerable<IManager> Managers { get; private set; }
[Inject]
private void Inject(IEnumerable<int> numbers) // Method name here does not matter
{
...
}
}Note that
InjectAttributealso works on non-mono classes.
Should be used to allow for source generation on the type being injected to, simply use it to decorate classes that implement [Inject] members:
[SourceGeneratorInjectable]
public partial class Foo : MonoBehaviour
{
[Inject] private readonly IInputManager _inputManager;
[Inject] public IEnumerable<IManager> Managers { get; private set; }
[Inject]
private void Inject(IEnumerable<int> numbers) // Method name here does not matter
{
...
}
}Note that
SourceGeneratorInjectablerequires that the decorated type be partial and public alongside all of its containing types:
public partial class Foo
{
[SourceGeneratorInjectable]
public partial class Bar : MonoBehaviour
{
[Inject] private readonly IInputManager _inputManager;
[Inject] public IEnumerable<IManager> Managers { get; private set; }
[Inject]
private void Inject(IEnumerable<int> numbers) // Method name here does not matter
{
...
}
}
}Can be placed on constructors, telling reflex which constructor to use when instantiating an object. By default its not required, as usually injected classes have a single constructor, so by default reflex tries to find the constructor with most arguments. But sometimes this can be required if you need more granular control on which construtor reflex should use.
If objects (plain old c# objects or unity objects) are created during runtime, theres no way reflex can detect this creation to auto inject the object, this needs to be done manually using one of the following methods:
AttributeInjector::void Inject(object obj, Container container)
// Injects given object fields, properties and methods that was annotated with Inject attributeConstructorInjector::object Construct(Type concrete, Container container)
// construct object of given type, using the constructor with most parameters, using given container to pull the constructor argumentsGameObjectInjector::void InjectSingle(GameObject gameObject, Container container)
// Optimized code meant to find injectables (MonoBehaviours) from a given GameObject, to then, inject using AttributeInjector
// This option injects only the first MonoBehaviour found on the given GameObjectGameObjectInjector::void InjectObject(GameObject gameObject, Container container)
// Optimized code meant to find injectables (MonoBehaviours) from a given GameObject, to then, inject using AttributeInjector
// This option injects all MonoBehaviours found on the given GameObject (not recursively, so it does not account for children) GameObjectInjector::void InjectRecursive(GameObject gameObject, Container container)
// Optimized code meant to find injectables (MonoBehaviours) from a given GameObject, to then, inject using AttributeInjector
// This option injects all MonoBehaviours found on the given GameObject and its childrens recursively GameObjectInjector::void InjectRecursiveMany(List<GameObject> gameObject, Container container)
// Optimized code meant to find injectables (MonoBehaviours) from a given GameObject, to then, inject using AttributeInjector
// This option injects all MonoBehaviours found on the given list of GameObject and its childrens recursively An alternative approach is to utilize the GameObjectSelfInjector, which can be attached to a prefab to resolve its dependencies at runtime. Through the inspector, you can select the injection strategy: Single, Object, or Recursive. Each strategy invokes the corresponding method in the GameObjectInjector class.
// Allows you to get a scene container, allowing you to resolve/inject dependencies in a different way during runtime
SceneExtensions::GetSceneContainer(this Scene scene)
{
return UnityInjector.ContainersPerScene[scene];
}
// Usage example:
var foo = gameObject.scene.GetSceneContainer().Resolve<IFoo>();It can be accessed from menu item Window β Analysis β Reflex Debugger, or from shortcut CTRL + E.
To enable reflex debug mode you must go to Edit β Project Settings β Player, then in the Other Settings panel, scroll down to Script Compilation β Scripting Define Symbols and add REFLEX_DEBUG. This can be easily achieved by clicking on the bug button at bottom right corner inside Reflex Debugger Window.
Note that debug mode reduces performance and increases memory pressure, so use it wisely.
Debugger window allows you to inspect the following:
- Hierarchy of Containers, Bindings and Instances
- Binding Contracts, Kind and Lifetime
- Binding Resolution Count
- Container construction call stack (who created the container)
- Binding construction call stack (who created the binding)
- Instance construction call stack (who resolved the binding making selected instance to be instantiated)
It's a ReflexSettings scriptable object instance, named ReflexSettings that should live inside a Resources folder.
It can be created by asset menu item Assets β Create β Reflex β Settings.
- logging verbosity is configured in this asset, and default value is set to
Info - the list of RootScopes is also configured in this asset, and default value is null
Important
ReflexSettings asset is obligatory to have
Resolving ten thousand times a transient dependency with four levels of chained dependencies. See NestedBenchmarkReflex.cs.
| GC | Time | GC Ratio | Time Ratio | |
|---|---|---|---|---|
| Reflex | 54.7 KB | 4.9ms | 100% | 100% |
| Zenject | 503.9 KB | 34.4ms | 921% | 702% |
| VContainer | 70.3 KB | 20.3ms | 128% | 414% |
| GC | Time | GC Ratio | Time Ratio | |
|---|---|---|---|---|
| Reflex | 140.6 KB | 4.0ms | 100% | 100% |
| Zenject | 1000 KB | 15.8ms | 711% | 395% |
| VContainer | 140.6 KB | 4.2ms | 100% | 105% |
| GC | Time | GC Ratio | Time Ratio | |
|---|---|---|---|---|
| Reflex | 140.6 KB | 0.7ms | 100% | 100% |
| Zenject | 1000 KB | 5.6ms | 711% | 800% |
| VContainer | 140.6 KB | 1.9ms | 100% | 271% |
| GC | Time | GC Ratio | Time Ratio | |
|---|---|---|---|---|
| Reflex | 140.6 KB | 1.4ms | 100% | 100% |
| Zenject | 1000 KB | 6.2ms | 711% | 442% |
| VContainer | 140.6 KB | 3.0ms | 100% | 214% |
If you are taking advantage of reflex to inject IEnumerable<T> in your constructors AND your are building for IL2CPP, you will probably get some exceptions like following:
System.ExecutionEngineException: Attempting to call method 'System.Linq.Enumerable::Cast<ANY-TYPE>' for which no ahead of time (AOT) code was generated.
This happens because compiler does not know at compile time that a specific System.Linq.Enumerable::Cast<T> should be included. And currently Reflex does not implement any type of assembly weaving.
Reflex 4.0.0 had and assembly weaver that was relying on unity UnityEditor.Compilation.CompilationPipeline events and Mono.Cecil. But it was causing conflicts with projects using Burst. So it's being removed temporarly until a definitive solution is found. Most probably we are going to weave assemblies the same way unity is doing for Burst as well.
Temporary workaround example:
class NumberManager
{
public IEnumerable<int> Numbers { get; }
public NumberManager(IEnumerable<int> numbers)
{
Numbers = numbers;
}
// https://docs.unity3d.com/Manual/ScriptingRestrictions.html
[Preserve] private static void UsedOnlyForAOTCodeGeneration()
{
Array.Empty<object>().Cast<int>(); // This compiler hint will get rid of: System.ExecutionEngineException: Attempting to call method 'System.Linq.Enumerable::Cast<System.Int32>' for which no ahead of time (AOT) code was generated.
throw new Exception("This method is used for AOT code generation only. Do not call it at runtime.");
}
}Ask your questions and participate in discussions regarding Reflex related and dependency injection topics at the Reflex Discord server.
Reflex is distributed under the terms of the MIT License. A complete version of the license is available in the LICENSE file in this repository. Any contribution made to this project will be licensed under the MIT License.


