0

I'm new at Zenject(Extenject).

My dev environment: Win10, Unity2020, Extenject 9.2.0

Here is my question:

In installer bind the class

Container.Bind<AccountInfo>().AsCached();

Inject it at classA

private AccountInfo accountInfo;             

[Inject]
private void Init(GameSetup _gameSetup, AccountInfo _accountInfo)
{
    this.gameSetup = _gameSetup; 
    this.accountInfo = _accountInfo;
}

accountInfo.address = "xxx'; // works fine

Then inject AccountInfo to classB

private AccountInfo accountInfo;      
        
[Inject]
private void Init(AccountInfo _accountInfo)
{
    this.accountInfo = _accountInfo;
}

accountInfo.address = "xxx'; //NullReferenceException: Object reference not set to an instance of an object

Why accountInfo changed to null? AsCached() dosen't work? Or something worng else?

Help please~~ Thank you!

Here is my code:

Installer enter image description here

"ClassA" inject GameSetup, and create instance, works fine enter image description here

"ClassB" inject GameSetup, Error: null object enter image description here

"ClassB" Creator, I'm trying use container.Instantiate() to create it enter image description here


---update--- gameSetup still Null Object enter image description here

Makyen
  • 31,849
  • 12
  • 86
  • 121
Stormer
  • 946
  • 7
  • 10
  • did you bind `classB` to the container¿?. I would check that and check if the `[Inject]` runs in the `classB` with a breakpoint in the line `this.accountInfo = _accountInfo;` – rustyBucketBay Sep 18 '22 at 13:31
  • Nope, I didn't bind classB to the container. I'll have a try. Thank you! – Stormer Sep 18 '22 at 13:34
  • Wait! ClassA also did not bind to the container. Why it works fine? ClassA is a consumer , it does not need to bind. – Stormer Sep 18 '22 at 13:38
  • Can you provide full code for classA and classB? It is not clear when `accountInfo.address = "xxx';` is called. Could it be called before `Init`? In constructor maybe. – Nikolai Sep 18 '22 at 15:31
  • Sure, I just updated. – Stormer Sep 18 '22 at 17:18
  • Please add code, errors, and data as **text** ([using code formatting](/editing-help#code)), not images. Images: A) can't be copy-&-pasted for testing; B) don't permit searching based on the code/error/data contents; and [many more reasons](//meta.stackoverflow.com/a/285557). In general, code/errors/data in text format are much, *much* better than code/errors/data as an image, which are somewhat better than nothing. Images should only be used, in addition to text in code format, if having the image adds something significant that is not conveyed by just the text code/error/data. – Makyen Sep 26 '22 at 03:36

2 Answers2

3

There are two cases, when injection will not work properly in your code.

  1. The code, that uses injected object is executed before Init. For example if this code is placed in the construcor.

  2. You create your GameObject/Component in runtime whithout using IInstantiator. While you use Znject you always should use IInstantiator to create objects. To do it you should inject IInstantiator to the object, that creates another objects. IItstantiator is always binded in the container by default, so you don't have to bind it manually. For example:


public class Creator : MonoBehaviour {

    [SerializeField]
    private GameObject _somePrefab;

    private IInstantiator _instantiator;

    [Inject]
    public void Initialize(IInstantiator instantiator) {
        _instantiator = instantiator;
    }

    private void Start() {

        // example of creating components
        var gameObj = new GameObject(); // new empty gameobjects can be created without IInstantiator
        _instantiator.InstantiateComponent<YourComponentClass>(gameObj);

        // example of instantiating prefab
        var prefabInstance = _instantiator.InstantiatePrefab(_somePrefab);
    }

}
Nikolai
  • 656
  • 6
  • 19
  • Thank you!!! 1. Nope, I didn't use injected object in ClassB's construcor. 2. ClassB is Non-MonoBehaviour. Maybe I can use Container.Instantiate() ? Like this: Container.Instantiate(); //In other class where I create ClassB's instance. But I can't get the Container object. – Stormer Sep 18 '22 at 16:49
  • Does ClassB inherit `MonoBehaviour`? If it is, so there is no way to do this. Moreover it is bad practice to use constructors with MonoBehaviours. But methods marked with `[Inject]` are called before `Awake()`, so you can place your initialisation logic inside `Awake()`. If ClassB is a regular C# class, that doesn't inherit `MonoBehaviour`, `Component` or other classes managed by Unity, so you can use constructor injection. To do this you don’t need to mark any method with `[Inject]`. Just add `AccountInfo` to the constructor arguments. And you still need create instances with `IInstantiator` – Nikolai Sep 18 '22 at 16:51
  • ---update--- [Inject]private DiContainer Container; //Dose not work, null object. – Stormer Sep 18 '22 at 16:57
  • "Does ClassB inherit MonoBehaviour?" No. – Stormer Sep 18 '22 at 16:58
  • So add `AccountInfo` parameter to the constructor, and create instance like this: `var instanceOfClassB = instantiator.Instantiate();` – Nikolai Sep 18 '22 at 17:05
  • Really thanks!~ let me have a try. By the way. I was edited this question, added my code. – Stormer Sep 18 '22 at 17:15
2

Not an expert but I think that passing IInstantiator or the container around is not a good practice. If you need to create injected instances at runtime, then you need a Factory. From the documentation

1.- Best practice with DI is to only reference the container in the composition root "layer"

Note that factories are part of this layer and the container can be referenced there (which is necessary to create objects at runtime).

2.- "When instantiating objects directly, you can either use DiContainer or you can use IInstantiator, which DiContainer inherits from. However, note that injecting the DiContainer is usually a sign of bad practice, since there is almost always a better way to design your code such that you don't need to reference DiContainer directly".

3.- "Once again, best practice with dependency injection is to only reference the DiContainer in the "composition root layer""

rustyBucketBay
  • 4,320
  • 3
  • 17
  • 47