What's the recommended approach for injecting to normal C# classes?

731 views
Skip to first unread message

Ferdinand Joseph Fernandez

unread,
Jan 16, 2016, 4:11:01 AM1/16/16
to StrangeIoC
I have this windowing system, it's a library that we use. In it, windows are standard C# classes, not derived from MonoBehaviour. I want to do injection to them but I found that it doesn't exactly work.

Here's a simplified code I made to tackle the problem:

public class BaseObject
{
}


public class InjectedObject1 : BaseObject, IView
{
 
[Inject]
 
public ITurnOrderManager TurnOrder { get; set; }


 
[PostConstruct]
 
public void PostConstruct()
 
{
   
Debug.LogFormat("InjectedObject1 injected! {0}", TurnOrder.ToString());
 
}


 
public bool requiresContext { get; set; }
 
public bool registeredWithContext { get; set; }
 
public bool autoRegisterWithContext { get { return true; } }
 
public bool enabled { get { return true; } }
}


public class InjectedObject2 : BaseObject, IView
{
 
[Inject]
 
public IAreaMap AreaMap { get; set; }


 
[PostConstruct]
 
public void PostConstruct()
 
{
   
Debug.LogFormat("InjectedObject2 injected! {0}", AreaMap.ToString());
 
}


 
public bool requiresContext { get; set; }
 
public bool registeredWithContext { get; set; }
 
public bool autoRegisterWithContext { get { return true; } }
 
public bool enabled { get { return true; } }
}


public class TestInjection : MonoBehaviour
{
 
void Start()
 
{
   
var obj1 = new InjectedObject1();
   
var obj2 = new InjectedObject2();
 
}
}

TestInjection is the usual MonoBehaviour class, and instantiates two normal C# classes, InjectedObject1 and InjectedObject2. They both implement IVIew by themselves, but I guess that's not enough.

Each of them are trying to inject a different kind of singleton. InjectedObject1 wants an ITurnOrderManager, while InjectedObject2 wants an IAreaMap.

I'm able to inject to usual View MonoBehaviour-derived classes, so I'm sure I set up my code right.

I just want to know what's the proper approach to injecting for normal C# classes (doesn't derive from MonoBehaviour) when I control their instantiation.

Ferdinand Joseph Fernandez

unread,
Jan 16, 2016, 12:07:36 PM1/16/16
to StrangeIoC
So I figured, me implementing IView for the 2 classes is useless, since IView is meant for MonoBehaviour-derived classes.

I just want to be able to inject to some normal C# classes. I get that normal classes can be injected into MonoBehaviour-derived classes. But having a normal class be injected into another normal class? Is that not possible/advisable?

wcorwin

unread,
Jan 16, 2016, 4:46:49 PM1/16/16
to StrangeIoC
Not only possible, but highly encouraged!

Nothing says you should be using MB for everything. Your life becomes far simpler if you don't. There's not much to add, except try to pull as much logic out of MBs as sanely possible. Injection is identical. Odds are high all of your injections will happen automatically, anyhow. You'll set them up with their bindings and it will be taken care of.

For reference, I'd say 80% of my code is plain ole c#. I use MB for views/mediators and that's it. Just stuff that shows up on the screen.

Ferdinand Joseph Fernandez

unread,
Jan 25, 2016, 6:31:21 AM1/25/16
to StrangeIoC
It's just that I got stuck in 1 part when attempting that. At the start, the view has to register itself to the context via context.AddView(). In MVCSContext, AddView tries to cast the view into a MonoBehaviour.

And it's kind of odd since it gets passed to the cacheView() method, but I don't see any reason why it had to bother being converted to MonoBehaviour there. It gets stored into viewCache which gets converted to an array of System.Object anyway upon being used.

Also, an MB View gets the context it belongs to by going to the parentage of gameobjects it's attached to. But I don't see how I can do the same for a normal C# class that wants to be a View.

I'm not sure how to modify things in the cleanest way possible. Anyway, I think I'll just work around this for now.

wcorwin

unread,
Jan 25, 2016, 1:51:57 PM1/25/16
to StrangeIoC
Right, the problem is you're using the mediationBinder to bind it :) That's specifically made for view components which are MonoBehaviours. InjectionBinder is what you need. Likely you're looking for: injectionBinder.Bind<MyClass>().ToSingleton();

Charlie Helman

unread,
Apr 12, 2016, 8:28:46 PM4/12/16
to StrangeIoC
I'm terribly sorry to revive an older thread, but I'm having the same issue and am not clear on the solution, even after your response.

Is it possible to use dependency injection in a normal (not strange) C# class?

In the strangerocks example, I see you have plenty of normal looking C# classes that are able to use the [Inject] class, or other attributes like [PostConstruct], etc. For us, however, they don't do anything. Not in our main project, or in a minimum repro project.

I feel like I'm missing something. Something strange ;)

Any ideas?

(Will upload min repro project if it helps)

Charlie Helman

unread,
Apr 12, 2016, 9:01:27 PM4/12/16
to StrangeIoC
To elaborate:

We have no problem binding something, the problem is injecting it in classes that aren't part of the MVCS framework.

public class NormalClass
{
    [Inject] // besides this inject tag, it's currently otherwise not tied at all to Strange.
   
public ISomeThing m_something{ get; set; }

   
public SomeFunction()
   
{
        m_something
.Foo() // null reference exception.
   
}
}

// meanwhile in something that derives from view or mediator:
public class SomeMediator : Mediator
{
   
[Inject]
   
public ISomething m_something { get; set; }

    blah blah
OnRegister()
    {
        m_something.Foo(); // this works fine; m_something is not null.
   
}
}


It seems like Injection in normal C# classes works just fine in the strangerocks example project. Granted, they're bound or otherwise implicitly bound to the context.. maybe that's why they work?

    [Implements(typeof(IScreenUtil))] // this is just a different way to create a binding, right?
    public class ScreenUtil : IScreenUtil // otherwise, this class not part of strange MVCS framework
    {
        [Inject(StrangeRocksElement.GAME_CAMERA)]
        public Camera gameCamera{ get; set; }
     // this is properly injected.

Perhaps a poor example.. since most things I can find that aren't MVCS but still utilize strange features seem to be tied to context somehow (in this case, it's implicitly bound, so I'm guessing the the context keeps track of ScreenUtil and handles its injections?)

Right now, these classes we want to add injection to aren't easily refactorable to fit into the MVCS sort of framework, or even keep track of / bind in the context, as far as I can tell.

wcorwin

unread,
Apr 12, 2016, 11:23:59 PM4/12/16
to StrangeIoC
Yup! Plain ole C# classes are the best kind of injections!

Your issue is likely finding the best way to inject them. You have a few options:

1) Bind them yourself and instantiate them using strange (recommended)
2) use the injector directly (less recommended) e.g. injectionBinder.injector.Inject(myObject);


I totally understand the issue of translating something to a framework, so I expect if you're still (or never) moving these classes over, you'll be using a lot more of #2 than #1. 

I would say 90% of my classes will be plain C# objects, and a very limited number of injected classes will be MBs. Basically just Views/Mediators and ContextViews. Let me know if I can help more!

Brett Unzaga

unread,
Apr 29, 2016, 1:12:06 PM4/29/16
to StrangeIoC
I'm running into the same issue as Ferninand and Charlie.  Could you please post an example of what the non-mediator/view/command class looks like, and where/how(what class and scope) you inject it, as well as how you are instantiating it?

Obviously, to gain access to the injector, we need to be inside a context, mediator or command, unless there is some other way to gain access to the injection binder?

The problem is that inside our non-strange classes, the properties aren't being injected to properly.  It is as though strange doesn't know about them.  The injected properties are null.

The two things I don't understand in your solutions are 
1. I tried to do injectionBinder.Bind<iCSClass>().To<CSClass>(); inside my main context.
But when I try to then [Inject] public iCSClass CSClass {get;set;} in my other class, it is just null.  How do I use strange to instantiate them?

2. How does the non-Strange class have access to the injector?  For example, if I want ClassA create a new ClassB.  ClassA was created by a mediator, but has no reference to anything Strange related.  So how does it create and inject a new ClassB?  Also, in your code "injectionBinder.injector.Inject(myObject);" am I passing in the instance of the myObject, or the class?

Some examples of using strange on regular c# classes would be a God send!

wcorwin

unread,
Apr 29, 2016, 1:34:09 PM4/29/16
to StrangeIoC
Sure, I'd be happy to help. A huge part of my codebase are classes which are plain ole C# objects. Sometimes those are just an assembly I made for a specific purpose (pathfinding or models, or whatever).

Generally, if isn't being injected it's because... we didn't call Inject on that object. Tautologies aside, this likely means it's not inside our ecosystem. You can either call injectionBinder.injector.Inject(myObject) directly, or instantiate it within the strange ecosystem.

Within the strange ecosystem means binding it in your context (or implicitly) and injecting it using [Inject] tags in another object in the strange-verse. Generally my non-MB objects are services, models, etc. Those are nearly always accessed in commands, so they're injected there.

My guess is if you're having trouble, it's because you have a section of your game which doesn't know anything at all about strange. That's ok, although when I separate out my games I'll try to give the outside world access to strange, even if it doesn't use it explicitly. Often I'll have an entity system tied to strange, and use strange for the UI and data management. The ES will handle its own business, but I'll inject things like an Entities or Components creator. This is probably too specific but if you're accustomed to ES maybe it will help.

As I said above, if you really just need to inject something that is outside our little world, a call to injectionBinder.injector.Inject(myObject) is all you need.


Brett Unzaga

unread,
Apr 29, 2016, 3:43:20 PM4/29/16
to StrangeIoC
Ok so assuming that injectionBinder is a ICrossContextInjectionBinder, the following should work:

a = new Achievement(atest); // atest is a json string
injectionBinder.injector.Inject(a);

Then in my Achievement I have this:

public class Achievement : iAchievement
{
    [Inject] public iAchievementManager AchievementManager { get; set; }

// other stuff

public void JsonImport(string aJsonString)
{
    Debug.Log(AchievementManager); // forever logs Null

I just can't seem to figure out the proper syntax or scoping or something. :/

wcorwin

unread,
Apr 29, 2016, 4:08:44 PM4/29/16
to StrangeIoC
it sounds a little backwards for your Achievement to need to know about its Manager.

To the actual problem, calling new on an Achievement will not leave it injected. You can create objects in strange a variety of ways.

i) If they're injected somewhere. Using a factory you can inject a new instance each time, or the first time a singleton is accessed and is null, it can be created
ii) injectionBinder.GetInstance<T>(). Pretty straightforward. Instantiate and inject.
iii) Pooling! This is a bigger concept, but check out some of the blog posts Marc made back when it was created.  You mentioned recycling objects, so tying injection in to the Pooling helps a bit.
iii) Mediation. Just listing this for completeness.

Brett Unzaga

unread,
Apr 29, 2016, 5:04:52 PM4/29/16
to StrangeIoC
(mordecai and rigby impersination)Ooooohhhhhh,,,,,  I got it working through GetInstance<>.  I was using that to get a reference to singleton instances, but I didn't realize we could use that to actually INSTANTIATE and get them :D

Yeah the AchievementManager injection was just to test if it works :D  It doesn't actually need to know about it.

A better example would have been GameData, which the achievement would check its goal values vs the current GameData.GameStats<string, string>.  The Achievement is a data object which has list of AchievementConditions.  When an event happens, if the condition is listening for that event, it checks the Achievements interested game stats vs the conditions goal values.  If all of the conditions have been met, the Achievement is granted to the user.

It is designed this way, so the server can create any achievement it wants, and grants any reward it wants, as long as the client is dispatching an event the Achievement is interested in.

So it's finally working :D  Thank you for the detailed help.

Everett Blue

unread,
Aug 9, 2020, 7:07:10 PM8/9/20
to StrangeIoC
Sorry to revive an old thread, but since this is the post I found when searching for an answer, I figured I should ask here so it's all in one place in case it ends up being helpful.  None of the previous posts seem to help with my issue, or maybe I'm not completely understanding the context of the answers.

In short, I want plain and abstract C# classes (some of which may have constructors) that can use [Inject].  It can be seen in "CharacterState" in my example below, which I am creating in Character (which implements ICharacter).  Based on other replies, I guess StrangeIoC isn't aware of CharacterState in order to properly use [Inject], but I'm not sure how to do that, and I don't want to be required to use interfaces for the State classes (the last 3 classes).

(Also I know the code can be improved in structure and also minor things like hardcoded values, but I wanted to keep it relatively simple.  I'm not asking about the structure, just how to simply inject into plain classes.)

public interface IStats //an interface (singleton) we want to inject in various places
{
   
void InitStats();
   
void UpdateEvasionStats(string isStanding);
}


public interface ICharacter
{
   
void UserInput(int input);
}


public class Character : ICharacter
{
   
[Inject]
   
public IStats Stats { get; set; } //not null (working as expected)

   
private CharacterState state = new StandingState(); //injectionBinder.GetInstance can't be used here since there is no injectionBinder reference

   
public void UserInput(int input) {
       
var newState = state.HandleInput(input);
       
if (newState != null) {
            state
= newState;
       
}
   
}
}


public abstract class CharacterState
{
   
[Inject]
   
public IStats Stats { get; set; } //null (this is the problem)

   
public abstract CharacterState HandleInput(int input);
}


public class StandingState : CharacterState
{
   
public override CharacterState HandleInput(int input)
   
{
       
if (input == 2) {
           
Stats.UpdateEvasionStats("crouch"); //this does not work
           
return new CrouchingState(true);
       
}

       
return null;
   
}
}


public class CrouchingState : CharacterState
{
   
private bool wasStanding { get; set; }

   
public CrouchingState(bool wasStanding) { //want to use a constructor
       
this.wasStanding = wasStanding;
   
}

   
public override CharacterState HandleInput(int input)
   
{
       
if (wasStanding) {
           
//some logic
       
}

       
if (input == 1) {
           
Stats.UpdateEvasionStats("stand"); //this does not work
           
return new StandingState();
       
}

       
return null;
   
}
}

Other responses say to use injectionBinder.GetInstance, but I don't have a reference to injectionBinder in any of the above classes, which is where I would want to replace the "new StandingState()".  Even then, how would I handle that for objects with constructors?  

There was also a mention of using "injectionBinder.injector.Inject(myObject);" which sounds promising, but I don't even know where that would go, and what it would mean to inject a single instance of an object.  If I had a reference to injectionBinder wherever I create "new CrouchingState(true);" would that allow me to use that "new" statement with the contructor, and still allow for the binding to happen, almost like a two step injectionBinder.GetInstance()?  If so that would be great, but again I'm not aware of a way for "public class Character : ICharacter" to reference injectionBinder even though it can use [Inject].

Am I right to be chasing a way to get a reference to injectionBinder in my "Character" class?  If so, how would I do that, or otherwise how can I get the State classes like CrouchingState to have the [Inject] attribute to actually get a reference to the singleton as the Character class can?

Any help would be greatly appreciated, I feel completely stuck on this one.

Will Corwin

unread,
Aug 9, 2020, 7:55:10 PM8/9/20
to Everett Blue, StrangeIoC
Ok, I have a few quick thoughts:

1) This isn't really a Strange issue, although obviously it seems like it.
2) I don't think your character anim state should ever need a reference to the character stats.
3) There is no way to magically inject something that was created manually using new. You have to inject it somehow, by either calling injectionBinder.injector.Inject(object) or as you noted, GetInstance which will call Inject after creating it.

I'd suggest using Commands for state management, and letting your models be dumb models. Strange is built as an event/data-driven architecture. Try to think in terms of the data, and you'll find the code kind of writes itself.


--
You received this message because you are subscribed to the Google Groups "StrangeIoC" group.
To unsubscribe from this group and stop receiving emails from it, send an email to strangeioc+...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/strangeioc/d83b4e8e-478e-48cd-a77d-85512c83ad77o%40googlegroups.com.

Everett Blue

unread,
Aug 12, 2020, 4:56:07 AM8/12/20
to StrangeIoC
Hey thank you so much for the quick reply.  I took some time to think it through some more and play with some ideas, but I'm still not getting anything that works well.  I understand what you're saying about using an MVC pattern, and I do use that in some areas, but at other points I have normal C# files which are just business logic, and in this case the business logic happens to reference classes used as part of a state pattern.

To quickly address point 2, I agree using Stats doesn't make sense there, but I didn't bother thinking through what may make more sense since the idea was only to create a simple example to illustrate wanting to inject some other class (regardless of what it is, those kinds of details can always change).  I probably should have just called it ISomeInterface.  My actual case isn't too complicated, but it feels more abstract so I wanted something simpler.

I'd suggest using Commands for state management, and letting your models be dumb models.

I could use commands to manage state, but it doesn't seem like there is an easy way to implement a state pattern, as each of the state objects don't have access to injectionBinder, which is a problem as they need to create new state objects to return the new state.  I really like commands and use them elsewhere in my project, however they do feel rather one dimensional (which I think is fine in general), and while I could put all the logic into it (in fact I had something equivalent as a first iteration implementation), it doesn't seem to be a great fit for this case as far as I can tell.

Strange is built as an event/data-driven architecture. Try to think in terms of the data, and you'll find the code kind of writes itself.

I appreciate the recommendation, and it definitely got me thinking about it from a different angle, although ultimately I was hoping I could pick and choose more even if it's not seen as optimal as far as Strange is concerned.

If you don't mind me asking, based on your knowledge do you think it might be feasible for me to explicitly force injection as needed, like maybe by allowing any normal C# class to inherit some parent "Injector" that would give it access to injectionBinder so that I can call GetInstance?  I tried forcing that concept by inheriting CrossContext to get the injectionBinder object, but it complained about there being no binding so maybe that concept can't work.  And/or if I would somehow be possible to add a line to my GameContext.cs (child of SignalContext) to reference the class and make StrangeIoC aware of it, and ready to [Inject].  In other words, can I basically say "Hey Strange, look at these objects and care about them!  Let things get injected into them!"  Do those ideas sound like impossibilities for me to achieve?  Thanks once again for taking the time to respond.
To unsubscribe from this group and stop receiving emails from it, send an email to stran...@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages