Dependency Injection in Unity [Inversion of Control Container]

Update Feb 6th, 2024:


“Dependency Injection” is a broad and confusing term, this thread is really discussing 2 different topics.

  1. We’re discussing the merits of an IoC Container Framework like Zenject or VContainer in Unity.

More on IoC Containers: Lightweight IoC Container for Unity - part 1 - Seba's Lab

  1. We’re discussing Dependency Injection in general.

“DI is just… data passed to an object at time of construction.”

Unity provides DI solutions based on this simple descriptor. Could it do a better job? or is it just fine as it is.

One example given is Sisus’s ‘Init Args’: Init(args) | Utilities Tools | Unity Asset Store

Update May 2nd, 2023:


Unity shouldn’t support Dependency Injection (beyond maybe introducing [SerializedInterface])
VContainer or Extenject if you want to use it.

Original:


There’s very few options for using Dependency Injection in Unity. Building games using Dependency Injection is really fun, and is a good way to decouple code.

It has the added benefit of your bugs showing up early (resolving the dependency graph) instead of happening some time down the line (after your game ships). It makes testing much easier as well, and replaces the need for:

  • Gameobject.Find()
  • GetComponent()
  • Singleton Managers
  • Null Reference Exception for Gameobjects/Components.

Scriptable Objects is not a long term solution to the issue, they’re not ideal for dynamic object creation (you really want 1000 scriptable objects for every bullet?)

Dependency Injection Container can handle that problem no issue.

ECS is great, for the people who are dedicated enough to use it. However I predict the vast majority of Unity Developers will not use it.

Does anyone else want a framework that makes S.O.L.I.D possible? I’m hoping one of Unity’s 3500 engineers sees this.

2 Likes

Having spent entirely too much time on these forums I’m of the opinion that this will be the same for people who use or are considering to use dependency injection. There have been discussions in the Scripting subforum concerning DI and the people who I thought were most likely to use it are the people who aren’t using it.

https://discussions.unity.com/t/912511/3

4 Likes

There’s very few options for using Dependency Injection in Unity.

We had MDADigital who has a strong advocate of it, until he got banned.

At the moment I’d recommend to avoid dependency injection and keep things simple. If you REALLY love it, you’re free to develop and use your own framework.

On top of that SOLID is not a principle I’d recommend to strongly adhere to, as there are plausible scenarios where it an make your codebase less readable by spanning too many entities. KISS is much more reliable in comparison. So I would not be interested in unity trying to strictly follow SOLID.

The problem with dependency injection is that as they say “it is a 50 dollar name for 5 cent idea”.
For example, if you take a look here:

“Construct injection” is a thing I’d expect any program to do intuitively, same goes for “Setter Injection”.

Now, “Assembly” example is actually a bad thing, as you’re passing arguments as string, and by doing that you lose compile time checking.

For the record, C# gamepad example is the very definition of inheritance bloat, and I’d not recommend it.


So rather than promoting dependence injection, which is also somewhat vaguely defined, it would be better if you specified which functions you’d want to be added. In this case it’ll be possible to actually discuss something.

5 Likes

More than enough. There is no need Unity spending valuable money and time on this.

And that’s the problem. I imagine it is fun for you, but it is not good for game development.
Unity should propagate techniques which aren’t necessarily fun, but they are actually good for game development.

5 Likes

That may well be the case, but it’s not due to a lack of dependency injection in particular, it’s due to a lack of software design experience in general. If you’re thinking about that stuff there are multiple decent approaches, of which DI may well be one.

The implication that SOLID is impossible in Unity without a DI framework, or that using SOs to configure bullets requires thousands of them, make me wonder if there’s a misunderstanding somewhere?

If that’s what works for your team then go for it. In most cases, though, teams prefer configuration to happen outside of code, and the flexibility to swap out components rather than relying on a programmer (not everyone making a game is one!) to come along and change that line of code.

DI is useful, but it’s no more The One Solution to Rule Them All than ScriptableObjects are.

4 Likes

There’s a huge problem with your example. It looks like this

public class Foo : MonoBehaviour
{
    [BlackMagicVoodoo]
    private Dependency myDependency

}

What does the attribute DO? Where does it happen? What is its purpose?

The purpose of attributes is to attach metadata to fields, and when metadata vastly alters program behavior and overwrites variable initialization, I’d say you’re breaking both KISS and Principle of Least Astonishment.

In real-life scenario your second example would look like this:

public class Bar: MonoBehaviour{
    [SerializeField]Dependency dependency;
    void Awake(){
        if (!dependency){
            throw new System.ArgumentNullException($"Dependency not set in {gameObject} on {this}");
            enabled = false;//optional
        }
    }
}

It is not your program’s job to recover from invalid state. If the dependency is not set, but is required, then the program should throw.

Additionally, it is not a good idea to provide setters unless they’re REQUIRED. That’s according to YAGNI.

Likewise, in Unity engine, OnEnable and Update methods are usually private. Because they’re are meant to be called only by the engine and not other components.


Regarding this part:

Normally you’re supposed to change zero lines of code in this scenario. Unity provides facilities for abstracting away input devices, so you’d be using input actiosn while being unaware where the input is originating from.

2 Likes

I would argue that specifically for less experienced developers, DI will make programming in Unity actually more confusing due to the indirection and because of possible DI overuse.

DI encourages throwing dependencies into a class because it’s just so damn easy to do. Similarly, it discourages thinking about dependencies in the first place, where they come from, and when and how, and what that does to your software architecture. And what about lazy initialization? Not every dependency needs to be resolved right away, or only in some conditions (editor vs playmode, target platform). It is sometimes okay or indicative of a given state when some dependency is and remains null.

In the “wrong hands”, DI can quickly lead to a mess of highly coupled components that inject themselves into each other and plenty of issues that are hard to understand because of the indirection when resolving dependencies “magically”.

4 Likes

DI also has negative impact on loading speed. The more classes you have - the slower it gets over time.
Which definitely not a good idea on a mobile. This can skyrocket to 30s of waiting easily on medium-large project which DI is usually self-promotes organizing towards.

Not many people know, but Unity already uses service location.
Use OnValidate instead of Awake for extra checks & component fetching. And you’ve got yourself free hierarchy / prefab based component location which is more robust and more transparent that what DI does. With 0 extra cost on top of what Unity already does during initialization. Plus fully automatic, so no dragging & dropping is required if prefabs are built correctly.

Everything performed in OnValidate is performed only during editor time. Which also allows to figure out what components are missing on what objects fairly easily without running game on the target device.

Expand editor - and you’ve got a pretty robust tool to handle complex checks & dependency management with an ability to show what is wrong to the game designer. Transparent, easy to use, and easy to understand.

If you want “real” usable DI - use Entities instead. DOD allows to execute logic only when specific components are present, processing data pretty much across entire application without black magic of injects involved. Plus, performance.

3 Likes

Question to OP, are you working with a team of programmers on a project other than small?

I feel like proposed solution for DI will introduce a mess to the project, where multiple devs start messing around with DIs all over the place.

Assemblies purpose is for decoupling, not DIs.

1 Like

DI is great for services dependencies, but not so great for GameObjects instantiation.

2 Likes

Why is it that ECS requires dedication to use, but DI does not?

So far in my journey as a solo-developer, I’ve found ECS to be the simplest paradigm to follow because it seems to standardize code such that entire project can follow same patterns over and over without many (if any) edge case caveats.

2 Likes

My understanding based on ECS experience is, that ECS constraints enforce design and data organization.
While in case of DI, you have more freedom, which opens higher opportunity to the mess in the design.

2 Likes

The attribute marks the method, field or method to be injected by the IoC container during initialization using reflection based on a binding contract. It marks the field as injectable in simpler terms. If there’s no IoC container to inject the field, then nothing will happen.

The advantage of this is when all the dependencies are created at the composition root it’s a lot easier to share them among users. But I understand there’s limitations to this.

what if your GameObject is a prefab, or loaded in an additive scene? [SeriaizedField] won’t work in this case for mono-behaviour references.

I agree, but the nature of the architecture means you’re not going to know when it’s going to throw. You might develop a system and 6 months later break it with some other change, it doesn’t get picked up in testing, gets published and then 5% of users hit an edge case where the game crashes. This is probably the most common bug in large unity projects.

[/QUOTE]

My example didn’t explain my point very well and for that I apologize. What I meant to say is I could change the input logic based on the same input, or change how the players health is handler, or how the camera should behave.

I agree, I like it for its flexibility which is exactly why it can get out of hand.

This can be alleviated with sub containers to ensure you’re only loading the dependencies you need for the immediate task, it’s still slow though I agree. Although some frameworks are using source generators to skip reflection.

I think that’s a great summary, and for the record I think ECS is the best framework for game design, but my Velocity is so damn slow using DOTS, plus I’m pretty sure you have to resort to OOP for some parts of the game anyway.

I’ve changed my opinion somewhat after reading this:

They built the first IoC Container for Unity in 2012, which was the basis for Zenject and many other IoC Frameworks. 10 years later in 2022 they say do not use IoC Containers and they’ve fully switched to their own version of ECS.

If the guy who brought IoC to Unity is telling us not to use it, then I should probably listen. I will check out their ECS framework as I find DOTS too difficult to work with.

3 Likes

What do you mean by Velocity in this context?

Depends on the design. You can limit the need of OOP design to handful of scriprlts, when operate with DOTS.

There are multiple ECS frame works.
But usage of them alone may have low value in comparison. Entitias for Unity is probably really good ECS framework, before DOTS was introduced. It just to be paid.
But you loose an opportunity of using jobs and burst in meaningful way, withouth putting much effort in the design. As this last two are critical before ECS, to get best performance.

1 Like

Are you referring to just the ECS aspect of DOTS? Or are you referring to the entirety of it? And which releases have you tried out? Because I remember back when DOTS was first showcased it was a much more verbose framework.

1 Like

I would argue that moving initialization into attribute breaks principle of least astonishment.

It will work when the reference point to within the prefab or within the scene.

Normally your “dependency” will be pointing at another object within the same prefab (i.e. it is a spawnable object), or in the scene (it is a scene object).
If the dependency is not an object that can be stored on the prefab or within the scene, the dependency becomes a scriptable object asset.
In addition we have lazy initialization, when it is necessary.

It will throw when you make a mistake in configuration. If you need extra validation, there’s OnValidate method brought up, I think, by xVergilx. The whole point of such system is to make sure it doesn’t break 6 months later.

A dependency injection with a string based config, on other hand, can absolutely break 6 months later in unchanged configuration if someone rewrite configs which do not go through compile check.

Change of input should still be handled through action map.
Change of camera begs the question - do you need extreme flexibility in the first place?

1 Like

It’s funny as I just realized that [SerializedField] is itself an attirbute that allows your to inject dependencies into an object.

The only difference between [Serializedfield] and [Inject] is how the dependencies are resolved.

I find [Inject] more versatile as you can inject plain c# classes, not just monobehaviours and scriptableobjects.

2 Likes

The difference is that SerializeField is well-defined in documentation and its behavior is known as part of unity API. Inject is not defined as an attribute and its behavior is not known.

Additionally, SerializeField does not vastly alter behavior of a field. Your description of Inject implies that it alters default initialization behavior to the point where it guarantees that fields will not be null which goes against expected behavior of the language in C#. This is comparable to overriding operator+ to return multiplication through side effect. That’s breach of “principle of least astonishment”.

At least that’s the way I see it.

Regarding plain C# classes, unless I’m missing something, System.Serializable makes them appear in inspector, and then there’s SerializeReference, which, IIRC was recommended at some point either by DragonCoder or by spiney199