Mef import into objects created after compose - mef

I Compose the Container at startup and later on create a Instance of a object that has a Import property. This property is Null when I try to use it.
How does MEF handle objects created later on. I refuse to believe you have to instantiate all objects at startup.

You don't have to create instances as soon as you create the container, that wouldn't be any real help for anyone. How are you creating your instances? Here are some examples, given an example class:
[Export]
public class MyClass
{
[Import]
public MyOtherClass OtherClass { get; set; }
}
I could:
var myClass = container.GetExportedValue<MyClass>(); // This would automatically compose.
var myExport = container.GetExport<MyClass>();
var myClass = myExport.Value; // This would automatically compose.
var myClass = new MyClass();
container.SatisfyImportsOnce(myClass); // Manually compose your part.
Or manually wind it all together using a CompositionBatch, etc.

Related

Extenject - NullReferenceException when second time inject

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
"ClassA" inject GameSetup, and create instance, works fine
"ClassB" inject GameSetup, Error: null object
"ClassB" Creator, I'm trying use container.Instantiate() to create it
---update---
gameSetup still Null Object
There are two cases, when injection will not work properly in your code.
The code, that uses injected object is executed before Init. For example if this code is placed in the construcor.
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);
}
}
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""

Why i need to declare a variable that have the same name of class and script?

public class CanvasManager : MonoBehaviour
{
public static CanvasManager Instance; // = lobby
[SerializeField]
private LobbyFunction _lobbyFunction;
public LobbyFunction LobbyFunction
{
get { return _lobbyFunction; }
}
...
below is one of the reference
private void Start()
{
GameObject lobbyCanvasGO = CanvasManager.Instance.LobbyFunction.gameObject;
...
I am confused that is it necessary to have the same name of canvasmanager that it is declared , and why there is no error when I sayCanvasManager.Instance.LobbyFunction ,it made me confused since LobbyFunction is belonged to CanvasManager, not Instance.
Finally , sometimes ,
private LobbyFunction _lobbyFunction;
private LobbyFunction LobbyFunction
{
get { return _lobbyFunction; }
}
Sometimes,
private LobbyFunction _lobbyFunction;
public LobbyFunction LobbyFunction
{
get { return _lobbyFunction; }
}
Thanks for your patience reading this, and your help would be greatly appreciated, thanks!
Your class is named CanvasManager, but you cannot statically access it right away.
You created a static member variable in CanvasManager, which holds a reference to a CanvasManager. This is called the singleton pattern.
You can only access static members without a class instance. But in the case of singletons, you create a single instance of the class (usually assigned in Start() or in getInstance() (lazy) after checking if it exists) which you can then access statically through "Instance".
Now, Instance is a static variable holding a reference to a single instance of CanvasManager. So, you can then access non-static members and functions of CanvasManager, if you access "Instance".
Think about it like this:
CanvasManager local_instance = new CanvasManager();
local_instance.non_static_member = value; // this works
CanvasManager.static_member = value; // this works
CanvasManager.non_static_member = value; // won't work.
And now one step further, you access the instance via CanvasManager.Instance.*
CanvasManager.Instance.non_static_member = value; // works!
Explanation of static vs non-static:
normal variables:
Variables needs memory. So usually you create 5 instances of CanvasManager and each instance can have different values. Because each instance reserves memory for each Variable. But if you want to change one, you need to explicitly talk to that instance. You could manage them in a List or by having multiple variables in Code like manager1, manager2...
Think of it as books, where each copy can be modified (write notes into it)
static variables
If you create a static variable, the memory is reserved once for the Class. You can then directly get/set this static variable from anywhere in Code without the need of a Reference to an instance.
Think of it as an online blog, where changes are applied for everyone, being accessible from everywhere. The text exists once in the blog database.
Singletons:
If you only want a single CanvasManager and not 5, you could attach it to any GameObject and access it. But every other script needs a reference, like public CanvasManager my_manager which you need to assign in inspector. As an alterantive, you could use
GameObject.Find("CanvasManagerObject").getComponent<CanvasManager>()
in each script... If only there was a better way to access this CanvasManager from everywhere...
The singleton pattern allows you to get a reference to a single, nonstatic instance of the CanvasManager, while it doesn't even need a GameObject it can attach to.
Naming
You are talking about "it has to have the same name" - this is not true. You can name the instance whatever you like. CanvasManager.MyCustomlyNamedInstance would work too. But the MyCustomlyNamedInstance must be a static variable in the CanvasManager class, or any other class. You could have a GameManager that manages your instances, so GameManager.MyCanvasManagerInstance would work too.

How to access Number through separate class?

Hey everyone so this is something that I have always had trouble trying to accomplish or understand. So I have my main Engine class calledescapeEngine where I have a private var nScore I want to be able to access this variables through a separate class called mcPlanets but I don't know how I would accomplish this. I know how to do the opposite but not how to access a var from my main Engine class. Can anyone help me out?
I am not sure what you are trying to do, but here is an example that may help you:
Inside esacapeEngine class (main), create a public var nString and new instance of mcPlanets.
// two lines in escapeEngine.as
var nScore = 0;
var mcPlant = new mcPlanets(this);
So, when you create new mcPlanets, pass in the reference (keyword 'this' in the parentheses). Now mcPlanets knows about your main class.
And now in mcPlanets class, write this:
public class mcPlanets
{
private var escapeEngine;
public function mcPlanets(main) // 'this' = 'main'
{
escapeEngine = main;
// access nScore defined in main class
escapeEngine.nScore = 5;
}
}
In this example, nScore must be a public variable, it could be a private but you should use 'get and set' methods.

MEF: how to import from an exported object?

I have created a MEF plugin control that I import into my app. Now, I want the plugin to be able to import parts from the app. I can't figure how setup the catalog in the plugin, so that it can find the exports from the app. Can somebody tell me how this is done? Below is my code which doesn't work when I try to create an AssemblyCatalog with the current executing assembly.
[Export(typeof(IPluginControl))]
public partial class MyPluginControl : UserControl, IPluginControl
[Import]
public string Message { get; set; }
public MyPluginControl()
{
InitializeComponent();
Initialize();
}
private void Initialize()
{
AggregateCatalog catalog = new AggregateCatalog();
catalog.Catalogs.Add(new AssemblyCatalog(Assembly.GetExecutingAssembly()));
CompositionContainer container = new CompositionContainer(catalog);
try
{
container.ComposeParts(this);
}
catch (CompositionException ex)
{
Console.WriteLine(ex.ToString());
}
}
}
You don't need to do this.
Just make sure that the catalog you're using when you import this plugin includes the main application's assembly.
When MEF constructs your type in order to export it (to fulfill the IPluginControl import elsewhere), it'll already compose this part for you - and at that point, will import the "Message" string (though, you most likely should assign a name to that "message", or a custom type of some sort - otherwise, it'll just import a string, and you can only use a single "string" export anywhere in your application).
When MEF composes parts, it finds all types matching the specified type (in this case IPluginControl), instantiates a single object, fills any [Import] requirements for that object (which is why you don't need to compose this in your constructor), then assigns it to any objects importing the type.

Compose part with specific instance

Is there any way I can compose (or get an exported value) with a specific instance as one of it's dependencies?
I have something like this:
public interface IEntityContext
{
IEntitySet<T> GetEntitySet<T>();
}
[Export(typeof(IEntitySet<MyEntity>))]
class MyEntitySet
{
public MyEntitySet(IEntityContext context)
{
}
}
// then through code
var container = ...;
using (var context = container.GetExportedValue<IEntityContext>())
{
var myEntitySet = context.GetEntitySet<MyEntity>();
// I wan't myEntitySet to have the above context constructor injected
}
I'm trying to mock something like entity framework for testability sake. Not sure though if I would want to go down this road. Anyway, should I be creating a new container for this very purpose. A container specific to the mocking of this one IEntityContext object.
So, if my understanding is correct, you want to be able to inject whatever IEntityContext is available to your instance of MyEntitySet?
[Export(typeof(IEntitySet<MyEntity>))]
public class MyEntitySet : IEntitySet<MyEntity>
{
[ImportingConstructor]
public MyEntitySet(IEntityContext context)
{
}
}
Given that you then want to mock the IEntityContext? If so, you could then do this:
var contextMock = new Mock<IEntityContext>();
var setMock = new Mock<IEntitySet<MyEntity>>();
contextMock
.Setup(m => m.GetEntitySet<MyEntity>())
.Returns(setMock.Object);
Container.ComposeExportedValue<IEntityContext>(contextMock.Object);
var context = Container.GetExportedValue<IEntityContext>();
var entitySet = context.GetEntitySet<MyEntity>();
(That's using Moq)
You can use your existing CompositionContainer infrastructure by adding an exported value.
Does that help at all? Sorry it doesn't seem exactly clear what you are trying to do...