Dependency Injection on Unity (Part 2)

Dependency Injection on Unity (Part 2)

This is the long-awaited continuation of my previous post about dependency injection. If you haven’t read it yet, it’s strongly recommended that you do it now, as this post builds upon knowledge from the previous one.

Improving our dependency injection

If our previous post we had a functional dependency injection system although it had its limitations:

  • The actual injection was very manual, which is slow, error-prone and, why not say it, a pain in the ass for us lazy programmers.

  • We didn’t had control of the dependency injection order, which could potentially lead to us trying to retrieve dependencies that hasn’t already be registered.

So lets see what we can do to improve this.

Automatic dependency injection

Having to manually retrieve and store each dependency is not ideal, luckily for us there are many ways to automate this.

One common example would be specifying the dependencies as parameters in the constructor and then using reflection to obtain the parameters list and construct that object with them.

This would be very handy as having the dependencies in the constructor is quite commonly used and it allows to construct the object manually, which can be useful for unit testing for example.

The drawback is that MonoBehaviour doesn’t allow to use constructors, so this cannot be used with them and we need an alternative.

Attribute-based injection

You know how Unity uses the SerializeField attribute to know what private fields should be serialized? well, we can do something similar, having a custom InjectField attribute we can specify which fields we want to be automatically injected. Creating a custom attribute is easy, just create a class inhering from Attribute:

1
2
3
public class InjectFieldAttribute : Attribute
{
}

But the attribute itself does nothing, is just decoration. This is where reflection comes handy. For those who doesn’t know what reflection is, you can think of it as the ability of the code to know itself (something that people seeks all its life, c# code has it built in). It basically gives you a Type class that defines a type, with all the information about its fields, methods, properties, etc.

Every object has a GetType() and from there we can get all the fields, public or private and then check if they contain the InjectField attribute. If they do, we’ll just find the type of the field, get it from the dependencies collection and use the SetValue to establish it.

Since we can use the InjectField attribute on private fields too, and those are not inherited, we’ll start with the fields defined in the actual object type and then move up in the inheritance hierarchy and repeat the process.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
public object Inject(object dependant)
{
    Type type = dependant.GetType();
    while (type != null)
    {
        var fields = type.GetFields(BindingFlags.Public | BindingFlags.NonPublic 
            | BindingFlags.DeclaredOnly | BindingFlags.Instance);
        foreach (var field in fields)
        {
            if (field.GetCustomAttribute<InjectFieldAttribute>(false) == null) { continue; }

            field.SetValue(dependant, Get(field.FieldType));
        }
        type = type.BaseType;
    }
    return dependant;
}

We’ll use GetFields with these BindingFlags to get the fields we want: BindingFlags.Public and BindingFlags.NonPublic to get all the public, private or protected fields; BindingFlags.DeclaredOnly will get us only the fields declared in the actual type, not inherited ones; and finally, BindingFlags.Instance will get us fields used per instance, which basically all non-static ones.

We’ll also use GetCustomAttribute(false) because we don’t want the attribute in the inherited fields, although we shouldn’t get any due to BindingFlags.DeclaredOnly, but just in case we want to extend this to properties or anything like that.

That’s it, this Inject method will automatically resolve all the dependencies in our type, declared or inherited, so we lazy programmers don’t have to do it manually.

Previously we had our DependenciesCollection that contained both the Add and the Get, so we could add it there too, but that class is going to start doing too many things, thus we’ll split it. We’ll keep DependenciesCollection as what it is, just the collection, so it’ll be just the Add method, and we’ll create a new class DependenciesProvider for the Get and Inject method.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public class DependenciesCollection : IEnumerable<Dependency>
{
    private List<Dependency> dependencies = new List<Dependency>();

    public void Add(Dependency dependency) => dependencies.Add(dependency);

    public IEnumerator<Dependency> GetEnumerator() => dependencies.GetEnumerator();

    IEnumerator IEnumerable.GetEnumerator() => dependencies.GetEnumerator();
}

If we make DependenciesCollection be an IEnumerable, we can just copy the list of dependencies into DependenciesProvider. Why would we want to do this? We’ll see later on the benefits of having a two-steps process in the dependency injection.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
public class DependenciesProvider
{
    private Dictionary<Type, Dependency> dependencies = new Dictionary<Type, Dependency>();
    private Dictionary<Type, object> singletons = new Dictionary<Type, object>();

    public DependenciesProvider(DependenciesCollection dependencies)
    {
        foreach (var dependency in dependencies)
        {
            this.dependencies.Add(dependency.Type, dependency);
        }
    }

    public object Get(Type type)
    ...

Other types of injection

Attribute-based injection is not the only choice we have, as mentioned before we can specify our dependencies in a constructor or we can have a special method decorated with an attribute or a predefined name. We’ll just need to do a different kind of reflection using GetConstructors or GetMethods to find the one we want to call, then using GetParameters to build the array with the dependencies and finally calling Invoke with that array. Constructors are just special methods, so the code is quite similar in both cases.

You can use whatever method you like the most or you can have all three available. Some dependency injection frameworks offer several options for the user to choose, like Zenject.

My personal choice is the attribute based one, because I can use it with both plain classes and MonoBehaviours, although method-based one can also be handy, as it’ll also provide a point to do some initialization after the injection happens. Constructor based one is my least favorite, as it’s not compatible with MonoBehaviours and when inheriting it forces you to type again the dependencies and we lazy programers don’t like to repeat stuff.

Note about reflection

One drawback from using reflection is that it’s not very performant, so you might want to consider caching some stuff, for example, the GetFields query results. But you should only worry about this if you have lots of objects and you notice performance hiccups due to the injection.

Another potential issue is that, when building using IL2CPP, Unity strips what it deems as unused code. This can lead to problems when using constructor or method based injection, as Unity won’t know that those methods are used. If those problems arises, you can make your attribute inherit from Unity’s PreserveAttribute, use link.xml or any other of the possible solutions explained in the code stripping section of Unity docs.

Dependencies factories

So how do we use our brand new Inject method? We already had a factory function in our dependencies, so we just need to pass our DependenciesProvider in this factory function. We can create a delegate to avoid having to specify all the parameters on each use.

1
public delegate object Delegate(DependenciesProvider dependencies);

For easy of use and to avoid repetition, we’ll create some static methods to build this factory functions.

From class

1
2
3
4
5
6
7
public static Delegate FromClass<T>() where T : class, new()
{
    return (dependencies) =>
    {
        return dependencies.Inject(new T());
    };
}

This is quite simple, just a generic function to create an instance using a default constructor and calling Inject right after that. Using the new constraint allows us to call the default constructor.

From prefab

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public static Delegate FromPrefab<T>(T prefab) where T : MonoBehaviour
{
    return (dependencies) =>
    {
        var instance = GameObject.Instantiate(prefab);
        var children = instance.GetComponentsInChildren<MonoBehaviour>(true);
        foreach (var child in children)
        {
            dependencies.Inject(child);
        }
        return instance.GetComponent<T>();
    };
}

This is also quite straight forward, we instantiate the prefab, find all the MonoBehaviours in it and call Inject for each one of them. This might not be very memory efficient if the prefab is quite big and contains a lot of MonoBehaviours, so we might consider using a recursive function to traverse the transforms of all the GameObjects and find the MonoBehaviour one at a time.

From GameObject

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public static Delegate FromGameObject<T>(T instance) where T : MonoBehaviour
{
    return (dependencies) =>
    {
        var children = instance.GetComponentsInChildren<MonoBehaviour>(true);
        foreach (var child in children)
        {
            dependencies.Inject(child);
        }
        return instance;
    };
}

At last, but not least, we can also inject an existing GameObject in the scene, we just need to do the same as with prefabs, find all the MonoBehaviours and call Inject on them. Bear in mind that GetComponentsInChildren also returns the components in the calling object, so the children array will also contain the calling MonoBehaviour.

With these three we can cover a lot of use cases, and they also serve as a reference to implement some other. I’ll leave as an exercise for the reader to implement a factory function from Resources or Addressables.

Initialization order

Ok, now we have an automatic way to inject our dependencies, no more manual process. But that was just half of the problem, the other half is the potential initialization order issues, where and when do we register our dependencies so they’re available when we inject them.

In our previous post we had a DependenciesContext static class to store our DependenciesCollection but nothing else in there. Having it as a static class was not great either, so why not make it a MonoBehaviour instead? We can register all of our dependencies in there and use it as our single point of initialization, so we can control the order and be sure that all of our dependencies are in place before requesting them.

Remember when we took out the resolving dependencies part from our old DependenciesCollection and moved it into DependenciesProvider? Now it’s starting to make more sense as now we can define our dependency injection as a two steps process, register and resolve, and our DependenciesContext can take care of that:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public abstract class DependenciesContext : MonoBehaviour
{
    protected DependenciesCollection dependenciesCollection = new DependenciesCollection();
    private DependenciesProvider dependenciesProvider;


    private void Awake()
    {
        DontDestroyOnLoad(gameObject);
        Setup();

        dependenciesProvider = new DependenciesProvider(dependenciesCollection);

        var children = GetComponentsInChildren<MonoBehaviour>(true);
        foreach (var child in children)
        {
            dependenciesProvider.Inject(child);
        }

        Configure();
    }

    protected abstract void Setup();

    protected abstract void Configure();

}

So now we have this abstract class that defines our injection process and from which we can inherit from to implement our two steps initialization.

First, we implement our custom setup process where we register all the dependencies we need, which are stored in our DependenciesCollection.

With all of our dependencies already register, then we can convert them into our DependenciesProvider, and then begin the injection process. Since we’re a MonoBehaviour, probably in a scene, we want to make sure that all the rest of the MonoBehaviours are injected.

And then we implement our custom configure process where we can resolve the dependencies we need and start doing our work.

This is it, right? Now we have a perfectly controlled initialization order, or do we? What if we have a class that wants to use some dependency in its constructor? Or what if we have a MonoBehaviour that needs some dependency in its Awake method?

We currently don’t have control over the Awake execution order, and our dependencies are injected after the constructor or the Awake method is called, so how do we fix this?

Scene initialization order

Unity allows us to define the execution order of the scripts, to guarantee that some scripts will be called before others. We can do this in Project Settings, but we can also use the undocumented but wellknown DefaultExecutionOrder attribute:

1
[DefaultExecutionOrder(-1)]

Just add it to our DependenciesContext class and our Awake will be run before the other MonoBehaviours in the scene, guaranteeing that we can use the dependencies in their Awake methods.

Class initialization order

We have a method in c# to construct an object without calling its constructor using FormatterServices.GetUninitializedObject. We can then inject our dependencies and then use reflection to call the default constructor.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
public static Delegate FromClass<T>() where T : class, new()
{
    return (dependencies) =>
    {
        var type = typeof(T);
        var obj = FormatterServices.GetUninitializedObject(type);

        dependencies.Inject(obj);

        type.GetConstructor(Type.EmptyTypes).Invoke(obj, null);

        return (T)obj;
    };
}

It might look a little bit dodgy, but it’s perfectly safe and it’s not much different to what Unity does with its SerializeField. Plus we already had the new() constraint so we’re sure that we’ll have a default constructor to call.

Prefab initialization order

And finally, we can control the execution order of the Awake in a prefab, because the Awake is not called on instantiation if the GameObject is not active, so we can disable the prefab before instantiating it, inject our dependencies and then activate it to have the Awake called.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
public static Delegate FromPrefab<T>(T prefab) where T : MonoBehaviour
{
    return (dependencies) =>
    {
        bool wasActive = prefab.gameObject.activeSelf;
        prefab.gameObject.SetActive(false);
        var instance = GameObject.Instantiate(prefab);
        prefab.gameObject.SetActive(wasActive);
        var children = instance.GetComponentsInChildren<MonoBehaviour>(true);
        foreach (var child in children)
        {
            dependencies.Inject(child);
        }
        instance.gameObject.SetActive(wasActive);
        return instance.GetComponent<T>();
    };
}

We want to be nice, so we’ll preserve the active state of the prefab, instead of forcing it to be active right after, as this could lead to unexpected instantiation of dependencies.

Summary

This dependency injection thing is starting to look quite good: we have an automated way to inject our dependencies so no more manual work; we have a perfect control on the initialization order so we can be sure that our dependencies will be ready when we need them; and we have a nice DependenciesContext that is not longer static and allows us to use a two step process in the initialization plus can also serve as the entry point of our application.

I hope this two-part article has helped you to be unafraid of dependency injection, so now you can start enjoying its benefits, whether it’s unit testing, decoupling or just simply more organized code. You can see all the code from this article along with some sample usage in my GitHub repository called SimpleDependencyInjectionV2.

There might be a third part, covering a fully-fledged dependency injection framework based on what we have learn here, but there will definitely be more articles on some other scary-looking-but-worth-learning stuff like unit testing, async programming or fancy c# syntax, so stay tuned for more.

Comments

comments powered by Disqus