A while ago, I wrote a blog that was critical of the Unity IoC container. A lot of what I wrote I think still stands. One complaint, though, does need a (partial) retraction. I complained that Unity was reflection based instead of being delegate based. Well, this is true of Unity proper, but the Unity project includes a StaticFactoryExtension which provides an extension to do delegate based construction. This can be used like this:
container.Configure<IStaticFactoryConfiguration>().RegisterFactory<SqlConnection>(() => new SqlConnection());
This is very flexible, and does provide full functionality. It’s disappointing (to me) that the base functionality is reflection based, and an extension is required to use delegates (after all, the above syntax is a bit ugly), but at least the functionality is there. Still not what I’d consider a "light weight" IoC container, and I’m still hoping to see some IoC interfaces in the BCL, but this isn’t a bad container.
Anyway, because it’s still not my ideal container and since I need a container for the M-V-Poo library I’m working on, I’ve been working on my own container. I have a long way to go, to ensure the proper balance of features while maintaining a very lightweight code base, but I thought I’d share some of what I’ve come up with. So, in this blog post I’m going to provide the basis for a lightweight container that you can extend into a container that fits your needs. Before we begin, I’ll point out that there are a couple of other simple containers you can find in other blogs. This one is slightly different in design (and heavier), because I’m focusing on providing a lightweight container while still providing all of the plumbing necessary to extend it until it provides all of the same functionality as Unity and other larger containers.
I’ll start with the container interface. As I said in my critic of Unity, I’d prefer separate interfaces for both the locator and the container. However, I’m keeping things simple here and providing only a single interface. I’m also following some of the naming conventions from Unity, though this is not a clone of the Unity APIs. When designing the interface for the container, I provided only the most verbose version of each function. The other overloads are just convenience versions, and so I’m keeping them out of the interface. That might sound odd right now, but you’ll see in a moment that this is actually a fairly flexible design. That said, here’s the interface.
public delegate object ObjectFactoryCallback(IObjectContainer container); public interface IObjectContainer { IObjectContainer RegisterType(Type type, string name, ILifetimePolicy liftetime, ObjectFactoryCallback callback); object Resolve(Type type, string name); IEnumerable<object> ResolveAll(Type type); }
Oh, there’s an ILifetimePolicy type in there that we’ve not defined yet. Let’s look at it.
public interface ILifetimePolicy { object GetValue(IObjectContainer conatiner, ObjectFactoryCallback callback); }
If you’ve seen Unity, most of what’s here should be obvious. This is a minimalist design, with all of the fluff removed. Further, you’ll notice that RegisterType is delegate based instead of reflection based by the lack of a second Type parameter and the addition of an ObjectFactoryCallback delegate parameter. So, we have a very simple interface design, now let’s provide an implementation.
public sealed class ObjectContainer : IObjectContainer { private class Registration { public Registration(ObjectFactoryCallback callback, ILifetimePolicy lifetime) { Callback = callback; LifetimePolicy = lifetime; } public ObjectFactoryCallback Callback { get; set; } public ILifetimePolicy LifetimePolicy { get; set; } } private ObjectContainer _parent; private Dictionary<Type, Dictionary<string, Registration>> _registry = new Dictionary<Type, Dictionary<string, Registration>>(); public ObjectContainer() { } public ObjectContainer CreateChildContainer() { ObjectContainer child = new ObjectContainer(); child._parent = this; return child; } #region IObjectContainer Members public IObjectContainer RegisterType(Type type, string name, ILifetimePolicy liftetime, ObjectFactoryCallback callback) { Dictionary<string, Registration> registrations; if (!_registry.TryGetValue(type, out registrations)) { registrations = new Dictionary<string, Registration>(); _registry[type] = registrations; } Registration registration = new Registration(callback, liftetime); registrations[name ?? string.Empty] = registration; return this; } public object Resolve(Type type, string name) { object result = TryResolve(type, name); if (result == null && _parent != null) result = _parent.Resolve(type, name); return result; } public IEnumerable<object> ResolveAll(Type type) { Dictionary<string, Registration> registrations; if (_registry.TryGetValue(type, out registrations)) { foreach (Registration registration in registrations.Values) { yield return Resolve(registration); } } } #endregion private object TryResolve(Type type, string name) { Dictionary<string, Registration> registrations; if (!_registry.TryGetValue(type, out registrations)) return null; Registration registration; if (!registrations.TryGetValue(name ?? string.Empty, out registration)) return null; return Resolve(registration); } private object Resolve(Registration registration) { if (registration.LifetimePolicy != null) return registration.LifetimePolicy.GetValue(this, registration.Callback); return registration.Callback(this); } }
There you go, a complete IoC container that can be easily extended to have the full capabilities of Unity and other containers.
OK, I won’t leave you hanging. Obviously when given just this, it seems like a very poor implementation. Even for only a delegate based container, the convenience overloads are missing, which seems like it would make this awfully cumbersome to use. And I’m sure several of you are confused about how you could possibly extend this container. There’s no built in extension mechanism, like there is in Unity, and the container is sealed, for Pete’s sake. Well, don’t worry, I’ll provide more.
First, I’ll explain how to extend this. Instead of derivation or a fancy extension mechanism, I’m going to use extension methods. I created an ObjectContainerExtensions static class where I provided a host of extensions. First, let’s look at how you’d define some of the convenience overloads.
public static IObjectContainer RegisterType(this IObjectContainer self, Type type, ObjectFactoryCallback callback) { return self.RegisterType(type, null, null, callback); }
public static IObjectContainer RegisterType<T>(this IObjectContainer self, ObjectFactoryCallback callback) { return self.RegisterType(typeof(T), null, null, callback); }
Pretty simple, really. From those two examples, you should be able to implement all of the the other convenience methods. One very nice benefit here is that any IObjectContainer concrete type will get the added benefit of these methods with out having to implement anything.
OK, now what about instance registration? We’ll write another extension method for that, with a few convenience overloads (I’ll leave the overloads as an exercise for the reader).
public static IObjectContainer RegisterInstance(this IObjectContainer self, Type type, string name, object instance) { return self.RegisterType(type, name, null, c => instance); }
This starts to show off the power of the delegate based approach. Our concrete container only knows how to register a delegate, yet we’ve trivially implemented an extension method that allows you to register an instance.
Great, now what about adding reflection based registration? How can I possibly do that with only a delegate based container? Why, with some extension methods, of course. I’ll start with an InjectProperties method that’s similar in usage to the BuildUp method in Unity. It injects properties on an existing object by using reflection and looking for properties marked with an InjectAttribute (left as an exercise for the reader).
public static object InjectProperties(this IObjectContainer self, string name, object existing) { Type type = existing.GetType(); PropertyInfo[] properties = type.GetProperties(); foreach (PropertyInfo propertyInfo in properties) { object[] inject = propertyInfo.GetCustomAttributes(typeof(InjectAttribute), true); if (inject != null && inject.Length > 0) { object value = self.Resolve(propertyInfo.PropertyType, name); propertyInfo.SetValue(existing, value, null); } } return existing; }
Again, you can provide convenience overloads for this.
Now we have the functionality we need to do property injection via reflection, but we still need a way to do constructor injection. Obviously the Resolve method should do this, but we can’t extend that beyond providing special delegates when we register a type with the container. So, I’ll add a couple of private static methods that we’ll use as the implementation of our delegates later on. I’ll also provide a private static method that can be used to create an ObjectFactoryDelegate that constructs a Type via reflection.
private static ConstructorInfo FindConstructor(Typetype)
{
ConstructorInfo[] constructors = type.GetConstructors();
if (constructors.Length == 0)
throw newInvalidOperationException();
ConstructorInfo constructorInfo = constructors[0];
if (constructors.Length > 1)
{
int argCount = -1;
bool injectFound = false;
foreach (ConstructorInfo cur inconstructors)
{
object[] inject = cur.GetCustomAttributes(typeof(InjectAttribute), false);
if (inject != nul l&& inject.Length > 0)
{
if (injectFound)
throw newInvalidOperationException();
constructorInfo = cur;
injectFound = true;
}
if (!injectFound)
{
ParameterInfo[] parameters = cur.GetParameters();
if (parameters.Length > argCount)
{
constructorInfo = cur;
argCount = parameters.Length;
}
}
}
}
return constructorInfo;
}
private static object Create(IObjectContainer container, ConstructorInfo constructorInfo)
{
ParameterInfo[] parameters = constructorInfo.GetParameters();
if (parameters.Length == 0)
return constructorInfo.Invoke(null);
object[] parameterValues = new object[parameters.Length];
for (int i = 0; i < parameters.Length; ++i)
{
parameterValues[i] = container.Resolve(parameters[i].ParameterType);
}
return constructorInfo.Invoke(parameterValues);
}
private static ObjectFactoryCallback TypeFactoryAdapter(Typetype)
{
ConstructorInfo constructorInfo = FindConstructor(type);
if (constructorInfo == null)
throw new InvalidOperationException();
return c =>
{
object result = Create(c, constructorInfo);
return c.InjectProperties(result);
};
}
Given this private implementation, we can now provide the RegisterType overloads for reflection based construction (again, most of the overloads are left for the reader to do).
public static IObjectContainer RegisterType(this IObjectContainer self, Type from, Type to, string name, ILifetimePolicy lifetime) { return self.RegisterType(from, name, lifetime, TypeFactoryAdapter(to)); }
There you go, a lightweight container that provides a good portion of the functionality found in Unity. There’s plenty of room for improvement. The code provided is not production worthy yet, and there’s still functionality missing. For instance, Unity will construct a type through reflection even if the type isn’t registered with the container. All you do is call Resolve with a concrete class type, and the container will construct it and do appropriate injections. With the current code, it’s non-trivial to do this, because we’ve hard-coded the reflection into our ObjectContainerExtensions class. To keep this lightweight, I’d move that functionality out into it’s own extension class, and put it into a different namespace. This allows anyone to use the functionality when implementing extensions, but keeps them out of the normal view for consumers.
Oh, there’s one final thing to illustrate. You’ve seen the ILifetimePolicy interface, but how do you implement a policy? Here’s the requisite SingletonLifetimePolicy.
public class SingletonLifetimePolicy : ILifetimePolicy { private object lockObject = new object(); private object value; #region ILifetimePolicy Members public object GetValue(IObjectContainer container, ObjectFactoryCallback callback) { if (value == null) { lock (lockObject) { if (value == null) { value = callback(container); } } } return value; } #endregion }
I considered other designs here. Initially, I just provided delegate adapters instead of providing an ILifetimePolicy. This works great, but I think the usage would be a little foreign to a lot of C# developers who’ve never done any functional programming. I also considered designing the ILifetimePolicy interface so that it had an Adapt method instead of a GetValue method. This makes the API familiar to the user, but the policy developer still has to understand functional programming. I’m thinking that might be the more appropriate compromise, since it removes the concern about user’s reusing a policy instance in multiple RegisterType calls, which as you can see from the implementation above would simply not work. With a functional design, it’s not too difficult to provide implementations that are safe from this. I’m pretty sure I’ll go with that design in my own library, but I kept the implementation here simple, in order to not detract from the lessons meant to be learned.
Leave a comment