It’s been a little while since I last updated my D20 project series. So, I want to take a moment to “reflect” on the architecture pattern I had been using for managing dependencies. There is a bit of a play on words there, because I had so much fun with “reflection” in my previous post, that I decided to experiment a bit more on how it could be used for a something that feels like auto-injection.
Reviewing Old Patterns
Let’s start with a quick review of the dependency management patterns that I had used in the D20 project. One pattern is to “Program Against Abstractions”. Toward this goal, I always define an interface for new systems, and make all of my code work based on the interface rather than the concrete class. So for example, I want features around data management, so I would create an interface and a conforming class:
public interface IDataSystem : IDependency<IDataSystem> { // ... properties and methods defined } public class DataSystem : IDataSystem { // ... implemented properties and methods }
The important bit about the above example was to note that my interface inherited from another interface – in particular, this bit:
IDependency<IDataSystem>
Thanks to the generic IDependency interface, other code could “Register” or “Resolve” a dependency for a given abstraction (interface). It used static storage as an alternative for an inversion of control container.
// Injection code tells what concrete class to use for an abstraction IDataSystem.Register(new DataSystem()); // Consuming code obtains and uses a reference by the abstraction var dataSystem = IDataSystem.Resolve();
Overall, I think the pattern worked well, and I was pretty happy with the results. The only complaints I had while using it were these:
- Interface inheritance
- Injection maintenance
I didn’t love the requirement of interface inheritance, partly because it is somewhat visibly redundant – it uses a generic of itself, and partly because I would like the dependency pattern to be able to be completely separate of the systems themselves.
Injection maintenance was an ongoing burden. I thought over and over again, “I sure wish there was some way these dependencies could just inject themselves”. It was a bit of a pain point every time I had to create an “Injector” class with a long list of systems it was injecting. It was even more of a pain when I wanted to refactor something – its name, or location in the project hierarchy etc, since I tried keeping everything in parity. To further complicate the injection code, I could see signs that some systems had dependencies on other systems, and therefore there was a necessary “order” of injection that could be rather fragile. Simply injecting one before another doesn’t explicitly indicate that it “needed” to be injected in that order. I used comments to help, but I want something better.
How Reflection Can Help
I will begin by removing the need of the interface inheritance. We can alleviate it with a standalone class:
public class Dependency<T> { static T _instance; public static void Register(T instance) { _instance = instance; } public static T Resolve() { return _instance; } }
This is a very simple, but functional, version of what a Dependency class could look like. Optionally, it could be made a bit more robust by registering functions that returned instances similar to the original IDependency interface – I will save that for if and when I need it. Most of the time I only need to inject systems, and those are mostly able to be treated like singletons. This provides static storage for each generic type, while allowing us to always work with the abstractions.
Next, I would like to solve the injection maintenance issue. This will be handled by a combination of a new attribute, and a system to operate on all types having the attribute. First, here is the attribute:
using System; [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = true)] public abstract class InjectableAttribute : Attribute { public abstract void Inject(object instance); }
Note that this particular attribute targets classes or structs, and has another new feature that allows there to be multiple injections. So you could register a single system for multiple different interfaces if it made sense to do so.
You may have noticed that this attribute is abstract – we will make a concrete version in a bit that works with our new Dependency class, but for now I want to focus on the two pieces that work together. Any Type having an attribute that inherits from InjectableAttribute will be automatically instantiated and injected thanks to the next class:
using System; using System.Collections.Generic; using System.Linq; public class DependencyInjection { public void Init() { CoreDictionary<Type, object[]> injectableTypes = new(); foreach (Type type in GetType().Assembly.GetTypes()) { var attributes = type.GetCustomAttributes(typeof(InjectableAttribute), true); if (attributes.Length > 0) { injectableTypes[type] = attributes; } } var orderedInjectableTypes = injectableTypes.OrderBy(x => x.Key.GetPriority()); foreach (KeyValuePair<Type, object[]> pair in orderedInjectableTypes) { var instance = Activator.CreateInstance(pair.Key); foreach (var attribute in pair.Value) { ((InjectableAttribute)attribute).Inject(instance); } } } }
When you “Init” an instance of this class it will do two things. First, it will grab ALL of the Type definitions in your assembly that have one or more attributes inheriting from InjectableAttribute. It will store that type and its attributes in a dictionary.
Next, the dictionary pairs will be sorted based on the Type‘s “Priority” attribute. See my previous post on Attributes & Execution Order if you don’t know what that is. In short, it is another (optional) attribute that lets me be explicit about the order I want certain code to be run. In this case it is even used to define the order that my system classes will be constructed.
The really great thing about this new flow, is that no matter how many new systems I create, or what kinds of priority I want them to have, I never need to update this injection code. I just add an appropriate attribute in place and I can have confidence it will all occur when and how it was supposed to.
We can “complete” the pattern with a concrete injectable attribute – one which handles our new Dependency class. It will look like this:
using System; using System.Reflection; public class DependencyAttribute : InjectableAttribute { private Type _type; public DependencyAttribute(Type type) { _type = type; } public override void Inject(object instance) { var generic = typeof(Dependency<>); var specific = generic.MakeGenericType(_type); MethodInfo method = specific.GetMethod("Register", BindingFlags.Public | BindingFlags.Static); method.Invoke(null, new[] { instance }); } }
That’s all the “core” code that will be needed, and which could be easily reused among your projects. Next I will show what it would look like to use this new pattern. The earlier example of the data system could look something like this:
public interface IDataSystem { // ... properties and methods defined } [Dependency(typeof(IDataSystem))] [PriorityFixed(Priority.High)] public class DataSystem : IDataSystem { // ... implemented properties and methods }
As you can see in this version, the interface no longer needs to inherit from anything. The class is marked with two attributes. The Dependency attribute will mark this as a system to be automatically instantiated and injected, and the (optional) Priority Fixed attribute will make sure this system is created before anything that has a lower priority (everything else by default).
Somewhere, like a bootstrap scene or script you would want to initialize your systems – one line handles EVERY system, like so:
new DependencyInjection().Init();
Once this single line has run, you can grab a reference to an injected dependency like so:
var dataSystem = Dependency<IDataSystem>.Resolve();
Summary
In this lesson we reviewed the patterns I had been using for dependency management in my D20 project. While I like them, I felt like they could be improved by some new tricks with attributes. This lesson explored what that could look like, and the result is something that allows systems to be automatically created and injected using some explicit attribute markers. I’m excited about it, and would love to know what you think!
If you find value in my blog, you can support its continued development by becoming my patron. Visit my Patreon page here. Thanks!