9
votes

Unity3D uses GameObjects. You add components to these gameobjects, where a component is a script (in c# or js) that inherits a base class. Unity itself is written in native code. Components can't have a constructor, and instead use reflection to find if you have certain named methods (OnStart, Update, etc.).

Instead of making my eyes bleed with lack of constructors and other really annoying things, I figured I could do the following:

public class SomeGameBehaviour
{
    public SomeGameBehaviour(IGameObject gameObject) { }
}

(Monobehaviour is the base class)

public class ComponentWrapper : MonoBehaviour, IGameObject { }

..then I could grab gameObject.Transform or what have you from SomeGameBehaviour, while decoupling it from the Unity enforced retardary.

The Problem: I couldn't use the default injection behaviour because Components/MonoBehaviours don't and can't have constructors - it throws errors at you if you try, so I rolled my own Provider.

public class UnityProvider : IProvider
{
    public object Create(IContext context)
    {
        var go = new GameObject(context.Request.Target.Name, typeof(ComponentWrapper));
        var c = go.GetComponent<ComponentWrapper>();

        return c;
    }

    public Type Type { get; private set; }
}

I can see in the Unity editor that the gameobject gets created, and ComponentWrapper gets attached, however Ninject throws a null ref error at me which I can't figure out. It seems to be doing further stuff to either IGameObject or the Target that's upsetting the process.

NullReferenceException: Object reference not set to an instance of an object
Ninject.Infrastructure.Language.ExtensionsForMemberInfo.GetParentDefinition (System.Reflection.MethodInfo method, BindingFlags flags)
Ninject.Infrastructure.Language.ExtensionsForMemberInfo.GetParentDefinition (System.Reflection.PropertyInfo property)
Ninject.Infrastructure.Language.ExtensionsForMemberInfo.IsDefined (System.Reflection.PropertyInfo element, System.Type attributeType, Boolean inherit)
Ninject.Infrastructure.Language.ExtensionsForMemberInfo.HasAttribute (System.Reflection.MemberInfo member, System.Type type)
Ninject.Selection.Heuristics.StandardInjectionHeuristic.ShouldInject (System.Reflection.MemberInfo member)
Ninject.Selection.Selector+<>c__DisplayClass3.<SelectPropertiesForInjection>b__2 (IInjectionHeuristic h)
System.Linq.Enumerable.Any[IInjectionHeuristic] (IEnumerable`1 source, System.Func`2 predicate)
Ninject.Selection.Selector.<SelectPropertiesForInjection>b__1 (System.Reflection.PropertyInfo p)
System.Linq.Enumerable+<CreateWhereIterator>c__Iterator1D`1[System.Reflection.PropertyInfo].MoveNext ()
System.Collections.Generic.List`1[System.Reflection.PropertyInfo].AddEnumerable (IEnumerable`1 enumerable)
System.Collections.Generic.List`1[System.Reflection.PropertyInfo].AddRange (IEnumerable`1 collection)
Ninject.Selection.Selector.SelectPropertiesForInjection (System.Type type)
Ninject.Planning.Strategies.PropertyReflectionStrategy.Execute (IPlan plan)
Ninject.Planning.Planner+<>c__DisplayClass2.<GetPlan>b__0 (IPlanningStrategy s)
Ninject.Infrastructure.Language.ExtensionsForIEnumerableOfT.Map[IPlanningStrategy] (IEnumerable`1 series, System.Action`1 action)
Ninject.Planning.Planner.GetPlan (System.Type type)
Ninject.Activation.Context.Resolve ()
Ninject.KernelBase.<Resolve>b__4 (IContext context)
System.Linq.Enumerable+<CreateSelectIterator>c__Iterator10`2[Ninject.Activation.IContext,System.Object].MoveNext ()
System.Linq.Enumerable.Single[Object] (IEnumerable`1 source, System.Func`2 predicate, Fallback fallback)
System.Linq.Enumerable.SingleOrDefault[Object] (IEnumerable`1 source)
Ninject.Planning.Targets.Target`1[T].GetValue (System.Type service, IContext parent)
Ninject.Planning.Targets.Target`1[T].ResolveWithin (IContext parent)
Ninject.Activation.Providers.StandardProvider.GetValue (IContext context, ITarget target)
Ninject.Activation.Providers.StandardProvider+<>c__DisplayClass2.<Create>b__1 (ITarget target)
System.Linq.Enumerable+<CreateSelectIterator>c__Iterator10`2[Ninject.Planning.Targets.ITarget,System.Object].MoveNext ()
System.Collections.Generic.List`1[System.Object].AddEnumerable (IEnumerable`1 enumerable)
System.Collections.Generic.List`1[System.Object]..ctor (IEnumerable`1 collection)
System.Linq.Enumerable.ToArray[Object] (IEnumerable`1 source)
Ninject.Activation.Providers.StandardProvider.Create (IContext context)
Ninject.Activation.Context.Resolve ()
Ninject.KernelBase.<Resolve>b__4 (IContext context)
System.Linq.Enumerable+<CreateSelectIterator>c__Iterator10`2[Ninject.Activation.IContext,System.Object].MoveNext ()
System.Linq.Enumerable+<CreateCastIterator>c__Iterator0`1[SomeGameBehaviour].MoveNext ()
System.Linq.Enumerable.Single[SomeGameBehaviour] (IEnumerable`1 source, System.Func`2 predicate, Fallback fallback)
System.Linq.Enumerable.Single[SomeGameBehaviour] (IEnumerable`1 source)
Ninject.ResolutionExtensions.Get[SomeGameBehaviour] (IResolutionRoot root, Ninject.Parameters.IParameter[] parameters)
ObjectFactory.GetInstance[SomeGameBehaviour] () (at Assets/Scripts/Core/ObjectFactory.cs:31)
Grid.Start () (at Assets/Scripts/World/Grid.cs:27)
3
When you get this working you should test on your target platform as soon as possible. The various Unity3d platforms don't necessarily have access to the same mono features as the editor, especially the web player and the iOS player.Calvin

3 Answers

6
votes

Ninject executes the activtion actions after the object is created. In this case it is the property injection activation action. It seems that there is a problem in the reflection part that tries to get the the information if there is an inject attribute on a property of your object. Probably that's a bug in Ninject. But for further investigation I need some information:

  1. Are you on the latest version of Ninject (2.2.1.0)?
  2. Which version do you use exactly? (e.g. .NET 4.0 NoWeb)
  3. Can you debug the StandardInjectionHeuristic.ShouldInject of Ninject and investigate which property is causing the problem? And see what's special about this property? (e.g. is it a virtual property that is overridden, are there other properties with the same name, is it an indexer, ...)
5
votes

The author of this article http://outlinegames.com/2012/08/29/on-testability/ managed to port Ninject to Unity and he called it Uniject: https://github.com/banderous/Uniject

However I want to point out my (simple) solution as well:

http://blog.sebaslab.com/ioc-container-for-unity3d-part-1/
http://blog.sebaslab.com/ioc-container-for-unity3d-part-2/

I still need to thank Remo Gloor, because thanks to him I understood better the concept of IoC container.

0
votes

I want to add something about this (although my problem could be different), since I wanted to use ninject in an Unity3D project as well.

Unluckily Ninject does not work in Unity 3D 3.5 both using the mono develop compiled version and the source code (2.2.1).

However using the source code I understood the reason: First I had to disable manually all the code related to the NOWEB define (unluckily Unity3D does not support compile level defines, if I am not wrong) , but the real crash happens because of the NO_ASSEMBLY_SCANNING related code. Exactly in Kernel.cs at these lines:

#if !NO_ASSEMBLY_SCANNING
            if (this.Settings.LoadExtensions)
            {
                this.Load(new[] { this.Settings.ExtensionSearchPattern });
            }
#endif

I tried to disable this define as well and then Unity3D stopped to complain. Although after I did it the injection seemed to stop working:

StandardKernel kernel = new StandardKernel();   

kernel.Bind<IKernel>().ToMethod(context => kernel);

kernel.Inject(ObjectWhichNeedsKernel)

it did not work, the ObjectWhichNeedsKernel did not had the IKernel injected, but it could be just my code wrong.

So I think that Ninject is not lightweight enough for Unity3D :/