Closed canuszczykpace closed 3 years ago
Can you provide me with more information about your use case? How do you want to inject your dependencies, what does it mean t o have a "fake" constructor, and what is "Awake/Start time?"
Sure: In Unity 3D there is a class called a Monobehaviour
that I have no control over when it gets instantiated. There are two methods that can be overridden - Awake() and Start() that are always called if provided.
Currently there is an IOC container that I have been using called ADIC that has been written and designed specifically for Unity but it isn't being updated any longer and is quite buggy. The way it works is by providing an extension method called .Inject()
that attaches to MonoBehaviour
classes. We decorate a method with an attribute called [Inject]
and the .Inject extension method injects the parameters of the method in accordance to bindings to a container.
Pretty straightforward and easy to implement.
So the method is what I was calling a "Fake" constructor. Really just a method that will be present anytime I want things injected similar to a constructor.
Hope that makes sense.
Although method injection is not supported OOTB, there is a code sample that demonstrates method injection. If you use that sample code, you can enable method injection by making the following call at startup:
// InjectAttribute is your [Inject] attribute
container.Options.EnableMethodInjectionWith<InjectAttribute>();
After that, make sure you register all MonoBehaviour
implementations in the container. With Simple Injector's default behavior this is required to get the following Inject
method to work. But besides, registering those MonoBehaviours is advised because, it allows them to be part of the verification process. Your injection methods will take part of this verification process. Here's how to register those behaviours:
foreach (Type type in container.GetTypesToRegister<MonoBehavior>(someAssemblies))
{
container.Register(type);
}
That leaves you with the implementation of the .Inject()
extension method, which would be the following:
public static class InjectExtensions
{
// Don't forget to initialize this property at startup.
public static SimpleInjector.Container Container { get; set; }
public static void Inject(this MonoBehaviour behaviour)
{
if (behavior is null) throw new ArgumentNullException(nameof(behaviour));
if (Container is null) throw new InvalidOperationException("Container not set.");
var prod = container.GetRegistration(behavior.GetType(), throwOnFailure: true);
prod.Registration.InitializeInstance(behaviour);
}
}
The 'trick' applied in this Inject
extension method is the call to Registration.InitializeInstance
. This method exists apply initialization to an externally created instance. In most cases there is no initialization, because most classes will use constructor injection as their sole method of composition. Simple Injector, however, can be extended to do all kinds of post-constructor initialization, such as property injection or the above method injection. When calling InitializeInstance
the existing instance is fed to the initialization pipeline.
Be aware, however, that the use of method injection comes with the same downsides as Property Injection, which basically means: Temporal Coupling. And in the case of the Inject
attribute, it gets worse because you have to resort to the Service Locator anti-pattern.
But there might be little choice here, because Unity applies the Constraint Construction anti-pattern. When frameworks apply anti-patterns, there's often little left than stacking anti-patterns on top of them.
Let me start by saying that I have no experience in Unity3D whatsoever, so the following idea might be completely besides the point and unusable. However, an often applied technique when dealing with frameworks that apply Constraint Construction, is to make the constraint objects Humble Objects. You can consider Humble Objects to be part of the Composition Root. The idea is than to extract all logic and dependencies out of the Humble Object into a new component. That component will solely use Constructor Injection as its injection pattern. From within the Humble Object you request that new component from the Container and invoke its (sole) public method.
This method, however, makes a few assumptions:
Whether or not this is possible and leads to better a better solution, is something I can't decide, but perhaps this idea is helpful.
Wow - what a great answer. Thanks a million.
Humble Objects - I wish. :)
Unfortunately, Monobehaviours are attached to GameObjects which are drag and drop objects in the "scene"'s hierarchy. FWIW - This helps form a link between developers and technical artists as public properties in the monobehaviours are exposed in the IDE's property inspector. It's not too bad except for creating this anti-pattern as you say. With a little finesse I hope to steer my team away from creating the monolithic classes that the monobehaviours tend to encourage.
I'm trying to use Simple Injector with Unity3D. Their
Monobehaviour
classes are created by the system. Is there a way to make a fake "constructor" so that I can inject into it at Awake/Start time?