Search Unity

  1. Unity 6 Preview is now available. To find out what's new, have a look at our Unity 6 Preview blog post.
    Dismiss Notice
  2. Unity is excited to announce that we will be collaborating with TheXPlace for a summer game jam from June 13 - June 19. Learn more.
    Dismiss Notice

Question Any dependency injection framework recommend?

Discussion in 'General Discussion' started by Stormer2020, Sep 18, 2022.

  1. Stormer2020

    Stormer2020

    Joined:
    Sep 14, 2020
    Posts:
    92
    Hi~ I'm searching a DI framwork.

    I found Zenject, but it stoped update add support at 2020.

    Would you recommend a good solution? Thanks!
     
  2. Murgilod

    Murgilod

    Joined:
    Nov 12, 2013
    Posts:
    10,244
  3. Stormer2020

    Stormer2020

    Joined:
    Sep 14, 2020
    Posts:
    92
  4. luispedrofonseca

    luispedrofonseca

    Joined:
    Aug 29, 2012
    Posts:
    947
    From a recent quick search I did, it seems Extenject is currently the best solution.
     
    Stormer2020 likes this.
  5. Stormer2020

    Stormer2020

    Joined:
    Sep 14, 2020
    Posts:
    92
    Thank you~ I'm looking at VContainer: https://github.com/hadashiA/VContainer
     
  6. TheNullReference

    TheNullReference

    Joined:
    Nov 30, 2018
    Posts:
    268
    For anyone coming into this thread circa April 2023.

    Extenject is no longer being actively maintained :(
    VContainer is being actively maintained.

    I would still use Extenject as it has many features that VContainer does not!

    Things you can do in Extenject that aren't possible with VContainer (unless you're willing to extend the framework yourself).

    - Bind Memory Pools
    - Bind multiple interfaces to different concrete type based on conditionals
    - [InjectOptional] attribute (useful if an object doesn't always need a dependency)
    - Resolve dependencies from sub containers
    - Use the decorator pattern.

    As well as this, Extenject offers multiple ways to accomplish the same task, where as VContainer barely provides the one solution.

    Documentation is also much more scarce.

    VContainer is better when comparing features it ACTUALLY has (which is 10% of Extenject).

    Maybe if you really knew what you where doing and where happy to write a ton of DI boiler plate code you will find VContainer the better solution (again it can do everything Extenject can do, and in theory do it better, if you write the code yourself)

    I think a core issue is both Extenject and VContainer try to maintain standalone DI framework, where as we really need a DI framework that's user friendly and built specifically for Unity.
     
    Last edited: Apr 11, 2023
    Meltdown, Ryiah, Yakirbu and 4 others like this.
  7. joan_stark

    joan_stark

    Joined:
    Jul 24, 2018
    Posts:
    51
    It's so sad that Zenject/Extenject is not maintained anymore. Vcontainer lacks features and accessibilty that Zenject has. Not much hope on DI frameworks :(
     
  8. Greenhapi

    Greenhapi

    Joined:
    Jun 22, 2020
    Posts:
    3
  9. FaithlessOne

    FaithlessOne

    Joined:
    Jun 19, 2017
    Posts:
    330
    While Extenject/Zenject may not be maintained anymore it works fine in Unity 2022.3. I have been using it for almost a year now and have absolutely no issues.
     
  10. PanthenEye

    PanthenEye

    Joined:
    Oct 14, 2013
    Posts:
    2,116
    CodeRonnie likes this.
  11. Glader

    Glader

    Joined:
    Aug 19, 2013
    Posts:
    456
    Personally, for many years I always used AutoFac which is just a standard C#/.NET dependency injection and Inversion of Control library. I always thought it was the best. But it's not a drop-in solution, you'd have to integrate it yourself. I personally avoid much of the "made for Unity3D" solutions because to me they seemed not as great compared to their popular standard .NET counterparts. Of course this 10 years ago when I was looking for a DI/IoC library to use in Unity3D so maybe things have changed and the Unity3D specific libraries are great now compared to AutoFac.
     
  12. andyz

    andyz

    Joined:
    Jan 5, 2010
    Posts:
    2,285
    There is this: Dependency injection - .NET | Microsoft Learn

    My (somewhat off topic) question would be: Do you need DI/interfaces and how do you gain over basic singleton use?
    For example if you plan on replacing a complete module from time to time the the interface & DI or service locator makes sense, you can swap any time. If you have a special version of modules for testing - again makes sense.
    But if you only ever end up using 1 game-manager class, 1 server communication class etc, I am not sure what the added dev overhead of not using a singleton directly (no interface) gains you. So do you need it?
     
  13. Slashbot64

    Slashbot64

    Joined:
    Jun 15, 2020
    Posts:
    339
    If a dependency inject framework is so good why haven't Unity bothered doing something about it?
     
  14. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    21,425
    You mean the company that struggles to make proper systems? Unity already has something in place that is a form of dependency injection: the Inspector. Why they haven't gone beyond that is likely a case of they don't see a need to do it themselves but then this is a company that took years to provide an official object pooling system.
     
    Last edited: Jan 6, 2024
    Meltdown, SisusCo, CodeRonnie and 2 others like this.
  15. CodeRonnie

    CodeRonnie

    Joined:
    Oct 2, 2015
    Posts:
    531
    Thanks for the shout out!

    I am planning to submit version 1.1.0, hopefully today. I've been editing and re-writing the whole documentation, so it should be much easier to understand when it goes live. Thanks to the interest of a few I've been very motivated over the holiday season to work on polishing everything, and recording myself setting up a new demo project from scratch. I hope I can put out a video with a much better example project soon.

    I would also recommend Init(Args) because @SisusCo is very active and responsive to users here.

    Here are some of the other frameworks that I've seen:

    Zenject
    https://github.com/modesttree/Zenject

    Extenject (Fork of Zenject)
    https://github.com/Mathijs-Bakker/Extenject

    UniDi (Fork of Zenject/Extenject
    https://github.com/UniDi/UniDi

    VContainer
    https://vcontainer.hadashikick.jp/

    StrangeIoC
    https://strangeioc.github.io/strangeioc/

    Reflex
    https://github.com/gustavopsantos/Reflex

    Frictionless
    https://github.com/Claytonious/Frictionless

    Binject
    https://github.com/somedeveloper00/Binject

    Injecter.Unity
    https://kuraiandras.github.io/Injecter/documentation/injecter.unity.html

    adic
    https://github.com/intentor/adic
     
    Last edited: Jan 10, 2024
    LijuDeveloper, Maeslezo and Meltdown like this.
  16. CodeRonnie

    CodeRonnie

    Joined:
    Oct 2, 2015
    Posts:
    531
    As a treat for forum regulars who promote static singletons and service locators over DI, I wanted to highlight something I learned recently about DI frameworks for Unity. Most of them don't strictly do what they claim.

    This section of Zenject's documentation says: https://github.com/modesttree/Zenje...es--recommendations--gotchas--tips-and-tricks
    "If you want to instantiate a prefab at runtime and have any MonoBehaviour's automatically injected, we recommend using a factory. You can also instantiate a prefab by directly using the DiContainer by calling any of the InstantiatePrefab methods. Using these ways as opposed to GameObject.Instantiate will ensure any fields that are marked with the [Inject] attribute are filled in properly, and all [Inject] methods within the prefab are called."

    Both of the links are broken, on both Zenject and Extenject docs, but if you look you can find this page about factories: https://github.com/modesttree/Zenject/blob/master/Documentation/Factories.md

    Their solution is to tightly couple your code to the Zenject PlaceholderFactory class, which negates the whole point of using dependency injection with the exact same criticism that they have of statics, tightly coupled dependencies. :D

    They don't even mention how you would inject a prefab dropped into a scene while playing. I'm assuming because it's not supported. I'm of the opinion that if a DI framework can't handle that use case, then it's not actually a complete DI framework for Unity. From what I've seen most frameworks either don't support it, or they say that if you really need that then they provide a handy dandy service locator, or some other tightly coupled framework dependency!
     
    Meltdown and neginfinity like this.
  17. SisusCo

    SisusCo

    Joined:
    Jan 29, 2019
    Posts:
    1,342
    I appreciate the vote of confidence, thank you :)

    To be fair, I think the main criticisms brought up against the service locator pattern, like in the infamous Service Locator is an Anti-Pattern blog post by Mark Seemann, were never really so much about avoiding coupling with a particular framework, but more about the practical experience of using the API of the service locator as a programmer:
    1. ServiceLocator.Get<T>()
      implies that it can provide an instance of any type on request, but in reality it can only return some subset. There's no way to know which types it can return and which not, from just looking at the API. The compiler also won't be able to help.
    2. Similarly, if you want to create an instance of an object that uses a service locator for resolving its dependencies internally, the compiler won't be able to tell you whether it is possible or not in a particular context. The code will compile, but it may or may not throw an exception at runtime time due to some service not being registered with the service locator.
    One of the main benefits of dependency injection is that it can help make the dependencies of services explicit, and even go as far as making it practically impossible to create an instance of an object without providing it all its dependencies.
    Code (CSharp):
    1. var service1 = new Service1();
    2. var service2 = new Service2();
    3. //var client = new Client(); // <- does not compile
    4. var client = new Client(service1, service2); // <- compiles
    Zenject does (kind of) avoid the issues described by Mark Seemann with its factories:
    Code (CSharp):
    1. public class Enemy
    2. {
    3.     readonly Player _player;
    4.     readonly float _speed;
    5.  
    6.     public Enemy(float speed, Player player)
    7.     {
    8.         _player = player;
    9.         _speed = speed;
    10.     }
    11. }
    12.  
    13. public class EnemyFactory : PlaceholderFactory<float, Enemy> { }
    1. When creating the Enemy class, you don't need to spend time thinking how to resolve its dependencies, you simply declare them.
    2. If you try to create an instance of Enemy in code, you won't be able to do it without providing it with all its services - the compiler will make sure of it.
    However, since Zenject is at the end of the day, all about using a DI container to resolve dependencies instead of pure DI, most of these benefits will not actually really apply very much in practice. You can still find yourself in situations where you don't know if a component will or will not work when you hit Play Mode, as it can depend on what Context objects you've assigned to some components, and what code exists in various installers. In this sense, I think it is a step back from Unity's serialized field based DI, where you can in many cases clearly see all the other objects that a component depends on listed in the Inspector, and as long as you drag-and-drop an instance to each slot, you will know that the instance has been provided with all the services it needs.

    In Init(args) I've also tried to avoid the two downsides brought up by Mark Seemann, both on the code side, as well as the Inspector side.

    On the code side, it is possible to use pure DI, and the compiler will be able to help ensure that all dependencies have been provided properly.
    Code (CSharp):
    1. prefab.Instantiate(service1, service2);
    2.  
    3. gameObject.AddComponent<Client, Service1, Service2>(service1, service2);
    Services can also be configured using the inspector, in which case missing services are highlighted in red, and the user is warned about them both in edit mode and at runtime, and provided with (hopefully) helpful error messages describing what went wrong and how to fix it.
    Automatic Null Validation.png

    Problem #2 is still a thing in Init(args), when the Inspector is used to configure services; the compiler will not be able to warn about trying to instantiate a prefab with missing services. However, since that validation is still taking place in edit mode, I think that the pain point is at least alleviated to a large extend.

    I believe that is supported in Zenject using the ZenAutoInjecter component.
    ZenAutoInjecter.png
     
    Last edited: Jan 7, 2024
    Meltdown and CodeRonnie like this.
  18. CodeRonnie

    CodeRonnie

    Joined:
    Oct 2, 2015
    Posts:
    531
    Yeah, that's a good point. However, I feel like that's why nothing can beat constructor injection, and when we can't use constructors then we're just trying to recreate lost functionality. Most of the conventional wisdom comes from an environment where constructor injection is a legitimate alternative to ever using a service locator.

    My take is that the service locator should only be accessed from one place, namely OnEnable(), and you can just set enabled = false if a required type is not provided. The component will disable, sort of like an object failing to initialize if a constructor throws an ArgumentNullException. Of course, that places the burden on the user to handle usage of the service locator as expected, but personally I have no problem with expecting users to know how to use something and not abuse it. Call it from OnEnable(), if a dependency is required set enabled = false, similar to System standard library constructors throwing an ArgumentNullException when a null is passed via constructor injection. With constructors the object is not created, with OnEnabled the component remains disabled, and other code can check the enabled status.

    However, I understand your admirable efforts to provide users with guarantees about whether types will resolve. It's true that my methodology provides no such guarantees. If there is nothing at the composition root, you just get null references. I leave it up users to just know that potential and code appropriately. If constructor injection were possible, I would also steer everyone clear of service locators.

    Ah, good point! I was not exact enough with my wording the first time, an easy thing to do when discussing a complex topic. I should have said that they do not support it without adding a framework specific component to your game object. If Zenject were removed you would have to delete all of those components one by one, and then code a completely different DI solution into the dependent objects.
     
    Last edited: Jan 11, 2024
    SisusCo likes this.
  19. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,618
    That's not a good idea.

    Ideally, when you try to do that, your project should fail to compile. When you are shifting checks to runtime, you're losing safety mechanims provided by C# type safety system. A runtime error is the one that might not surface until the most inconvenient moment.
     
    Meltdown and CodeRonnie like this.
  20. CodeRonnie

    CodeRonnie

    Joined:
    Oct 2, 2015
    Posts:
    531
    That's fair, but the system is honestly only intended serve a very specific, single responsibility, and only due to the limitations of the Unity environment. That is to replace direct references to singletons and static classes.

    Those exceptional failures should explode right away when you hit play, pretty much every single time. They are the types of dependencies that are not strangely referenced in some far reaches of the application.

    If you were worried that it might happen, you could easily create a test to run with unit tests, or in the type of special scene that must be loaded first that many seem to need. However, it wouldn't really be required for the app to start, just a unit test to make sure all necessary dependencies are always assigned in the currently assigned injector asset.

    It is only meant to replace the handful of singletons that everyone currently uses, so they can be modular interface references instead. If it's referenced anywhere else, other than initializing a MonoBehaviour in Awake, then it's not really a supported use case.

    Oh, and the way my service locator is referenced, it is only intended to be directly referenced on a single line, again in Awake(). The entire service locator is 30 lines of open source code with no other dependencies. And, from that point onward, everything else is handled via loosely coupled interface reference to the depedency injector asset that's assigned in the editor.
     
    Last edited: Jan 10, 2024
    Meltdown likes this.
  21. Meltdown

    Meltdown

    Joined:
    Oct 13, 2010
    Posts:
    5,824
    Looks pretty good, thanks for mentioning it, I'm about to start the core UI code for my new game, so been looking at several DI frameworks.

    This looks well maintained, and is a fresh break from the xxxJect libraries and seems to have some performance improvements over xxxJect and VContainer.

    I'll let you know of my thoughts/findings over here at a later stage.
     
    Greenhapi and Ryiah like this.
  22. DragonCoder

    DragonCoder

    Joined:
    Jul 3, 2015
    Posts:
    1,740
    Haven't used a standardized service locator myself, but one solution that comes to mind for this is use an enum which describes what services are available.

    EDIT: Can't you just give all classes that support a service locator an interface and give the Get<T>() method a generic type constraint ("where" statement) so that the compiler only accepts those?
     
    SisusCo likes this.
  23. CodeRonnie

    CodeRonnie

    Joined:
    Oct 2, 2015
    Posts:
    531
    That's an interesting idea, but one of the main goals, for me, is to be able to request an interface. The compiler would complain that the requested interface is not derived from the interface type constraint, not that a concrete implementation of the requested interface doesn't implement the constraint.

    When the concrete implementation class implements the generic type constraint, and it implements the type of interface that dependent clients are going to request, the two interfaces it implements are unrelated. So, requesting the interface you want is unrelated to the generic type constraint interface, and it wouldn't work.

    If you were using the service locator to get the concrete type directly though, it would at least tell you if that type had added that constraint interface. The interface would just be kind of like a tag on the class though, without any real methods or properties. I'm not even sure if you can have just an empty interface.

    Also, I think the problem is not just with whether there is a class that can be injected, but validating and guaranteeing that an instance will be injected.

    Don't get me wrong, I'm not arguing that you need to provide that guarantee, but I think that's what's being argued. I feel like constructors also have no guarantee that they will receive what they need because an object argument can just be null. At least a constructor can throw an exception though, and not create the object at all.
     
    Last edited: Jan 8, 2024
  24. SisusCo

    SisusCo

    Joined:
    Jan 29, 2019
    Posts:
    1,342
    So, something like this:
    Code (CSharp):
    1. IPlayer player = ServiceLocator.Get<IPlayer>(Service.IPlayer);
    It feels a bit clunky to me, having to basically repeat the same type name twice, and then having to maintain the list of services in the enum. Also the compiler wouldn't be able to help catch copy-paste bugs like
    ServiceLocator.Get<IInputManager>(Service.IPlayer)
    .

    While the API is an improvement in the sense that it now offers more guidance about what services are available via IntelliSense, not making use of the type system makes it feel a bit too hacky for me to want to use it in practice.

    This would indeed be a valid solution to problem #1.
    Code (CSharp):
    1. public interface IPlayer : IService
    2. {
    3.     ...
    4. }
    5.  
    6. public interface IEnemy
    7. {
    8.     ...
    9. }
    10.  
    11. IPlayer player = ServiceLocator.Get<IPlayer>(); // compiles
    12. IEnemy enemy = ServiceLocator.Get<IEnemy>(); // does not compile
    It would come with a few downsides / limitations:
    1. Would not be able to have services of types that you can't modify, like, for example, Camera or Canvas, or one from a third party package. The adapter pattern could be used to work around this limitation, though.
    2. Would need to "taint" all the classes and interfaces that can be used with the service locator by making them implement/derive from the interface used as the type constraints. This might not be an issue in some projects, but if for example you want to create modular, reusable assemblies, that aren't coupled to a particular service locator system, this could be unacceptable. I guess the adapter pattern could be used to solve this problem as well in theory, but that would introduce a lot of additional complexity... :D
    3. The constraint would make it impossible to call the method with the generic type parameter of another generic method, unless the parameter also has the same type constraint. Sometimes when combining multiple generic systems, it can be useful to have the ability to hook them together like this.
    Code (CSharp):
    1. public T GetServiceOrDirectReference<T>()
    2. {
    3.     if(ServiceLocator.IsService<T>())
    4.     {
    5.         // Here we know T is a service, but since it doesn't have the required type constraint,
    6.         // the code still won't compile.
    7.         return ServiceLocator.Get<T>();
    8.     }
    9.  
    10.     return directReference;
    11. }

    A third solution I've seen used to avoid the first problem, is to list every available service as a separate public property on the service locator:
    Code (CSharp):
    1. public class ServiceLocator
    2. {
    3.     public Player Player => Get<Player>();
    4.     public IInputManager InputManager => Get<IInputManager>();
    5. }
    The first time I saw it, it felt weird to me, to have to go directly modify the service locator class every time you introduce a new service. Open/closed principle, and all that. But now it feels like it's not a half-bad way to go, if using the service locator pattern - it does mean you'll be able to get a lot more help from the compiler. It's almost like the singleton pattern, except you can also retrieve services by an interface they implement.
     
    Last edited: Jan 8, 2024
  25. DragonCoder

    DragonCoder

    Joined:
    Jul 3, 2015
    Posts:
    1,740
    That is an aproach that crossed my mind too, but I did not recommend that because that ads dependency of the (formerly generic) service provider on all service classes it wants to support.
    If we think in terms of asmdefs in Unity, that means that everything in your project that needs any service, is now also indirectly dependent on all of the services (with the consequence of recompiles whenever a service is changed etc.).
    From a runtime-access-performance perspective this may be the fastest solution though (if you ommit the Get<> call and directly use the reference which you gotta store somewhere anyways) and thus I have used it for "local" service providers.

    Btw. turns out ANY aproach also has downsides. Hence why so many DI- (and DI-circumvention) frameworks have popped up over the years and there's not one to rule them all :D
    Well, none, except the Unity editor itself.

    For that reason I personally prefer the least invasive and simplest solutions because that gives best chance to keep all required knowledge in my mind without confusion (or in a team project, best chance that the knowledge can be written down or passed on easily).
     
    Last edited: Jan 8, 2024
    SisusCo likes this.
  26. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,618
    I believe it is possible to rewrite ServiceLocator so you can do this, or write an extension method.
    Code (CSharp):
    1.  
    2. IPlayer player;
    3. ServiceLocator.Get(out player);
    4.  
    this way Get is generic, and you only type "IPlayer" once, Generic type is auto-determined from argument.

    I believe the idea of many such frameworks is to decouple framework completely. The moment you introduce interface, you create coupling. Then again, referring to ServiceLocator is also a coupling which is similar to singleton, because to use something you need to know it exists, and that's a coupling.

    So, with this sort of though in mind, ServiceLocator should be able to return anything. People can dance around this by using object or something similar and make locators that return anything. But that kills compile-time checking. Which you want to keep.
     
  27. SisusCo

    SisusCo

    Joined:
    Jan 29, 2019
    Posts:
    1,342
    Oh yeah, this possibility did shortly cross my mind as well, but then I had a memory leak in my brain.

    Using the API does feel considerably better this way to me:
    Code (CSharp):
    1. ServiceLocator.Get(Service.IPlayer, out player);
    Still using an enum here feels a bit peculiar for my taste buds.
     
  28. SisusCo

    SisusCo

    Joined:
    Jan 29, 2019
    Posts:
    1,342
    That is a good point.

    If the service locator was not static (could use the singleton pattern instead, for example), then this could be avoided using extension methods:
    Code (CSharp):
    1. // In assembly containing IPlayer:
    2. public static class ServiceLocatorExtensions
    3. {
    4.     public static IPlayer GetPlayer(this IServiceLocator serviceLocator) => ...
    5. }
    6.  
    7. // In assembly containing InputManager:
    8. public static class ServiceLocatorExtensions
    9. {
    10.     public static InputManager GetInputManager(this IServiceLocator serviceLocator) => ...
    11. }
     
    CodeRonnie likes this.
  29. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,618
    I'd say that using an enum is wrong. Because that limits what the service can create at compile time.

    Basically, there's an obstacle. Either you want a universal factory or you want compile time safety.

    If there's enum, then locator is not universal. Effectively it is a syntaxic sugar for calling player factory.
    If there's no enum, the factory could be universal, but then you won't have compile time checking as the information would need to be passed in some sort of runtime form, starting with strings and ending with System.Type. Then you lose compile time checking.
     
  30. DragonCoder

    DragonCoder

    Joined:
    Jul 3, 2015
    Posts:
    1,740
    This is a ServiceLocator though. In a project there is only a certain set of services.
    Indeed the idea here isn't to also use this to create enemies or decoration objects.

    Instead you use it e.g. to query the EnemySpawner instance.
     
  31. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,618
    The question here is why would you want a unified facade to locate all services, when their (base) types are known in advance and are set in stone.

    However, I'm not really looking for an argument right now.