Since I posted my Teeny Tiny Template parser last week, I figured I’d keep going and post about my Itty Bitty IoC container as well. I’m not going to attempt to define IoC or Dependency Injection in this blog post, that’s not the point. There are enough sites out there already that cover this pretty well. Go read Martin Fowler’s article "Inversion of Control Containers and the Dependency Injection pattern" if you want the full story.
This blog post is simply to introduce my own "ClassRegistry" class, a C# IoC container that does pretty much everything I’ve ever needed from an IoC container, and does it in 100 lines. This is another one of those classes I like to use as a "first resort". No it doesn’t have the power of something like StructureMap, but for a lot of projects that can be overkill anyway, and when a new project is starting up it’s more about establishing the pattern than nailing down specific tools. You can always switch to a different IoC container later on if you outgrow this one.
I got started on this when I say Oren Eini’s 15 line IoC container (here), and Ken Egozi’s 15 minute response (here). They got me thinking about just how simple an IoC container could be. Oren’s is interesting as an experiment, but it’s a little TOO minimal for normal usage. Ken’s is interesting as well, but it only does transient registrations. Mine does constructor injection for the following types of dependencies.
- Instance – Returns a specific provided instance of a particular dependency each time.
- Singleton – Creates a single instance of a dependency the first time it is requested, and returns it for all requests from then on.
- Transient – Creates and returns a new instance of the dependency each time it is requested.
- Delegate – Executes and returns the result of the provided method.
So, on to the code. first of all, lets get some supporting stuff out of the way. I’ve got a DependencyInfo class which encapsulates and describes an individual dependency using an enumeration of the supported types. I’ve also defined an attribute used to mark which constructor to call in the event that the choice isn’t obvious (More on that later). Finally, I have two dictionaries. The first will store all the DependencyInfo objects, and the second will store actual instances of the dependencies for the "Instance" and "Singleton" dependency types.
public static class ClassRegistry { [AttributeUsage(AttributeTargets.Constructor)] public class InjectionConstructorAttribute : Attribute { } private enum DependencyType { None = 0, // Type is unset Delegate, // A builder function Instance, // A specific instance Singleton, // Dynamically created singleton Transient // Dynamically created transient object } private class DependencyInfo { public object Dependency { get; private set; } public DependencyType DependencyType { get; private set; } public DependencyInfo(DependencyType dependencyType, object dependency) { DependencyType = dependencyType; Dependency = dependency; } } private readonly static IDictionary<Type, DependencyInfo> dependencies = new Dictionary<Type, DependencyInfo>(); private readonly static IDictionary<Type, object> instances = new Dictionary<Type, object>();
That’s all very interesting, but it doesn’t actually do anything. In order for the container to return things to us, first we’ll have to "register" the types we want it to handle. I’ve provided four Register methods, one for each of the dependency types.
public static void Register<TContract>(TContract instance) { dependencies[typeof(TContract)] = new DependencyInfo(DependencyType.Instance, instance); instances[typeof(TContract)] = instance; } public static void Register<TContract, TImplementation>() { Register<TContract, TImplementation>(false); } public static void Register<TContract, TImplementation>(bool isSingleton) { DependencyType dependencyType = isSingleton ? DependencyType.Singleton : DependencyType.Transient; dependencies[typeof(TContract)] = new DependencyInfo(dependencyType, typeof(TImplementation)); } public static void Register<TContract>(Func<TContract> builder) { dependencies[typeof(TContract)] = new DependencyInfo(DependencyType.Delegate, builder); }
The first Register method takes in a specific instance of a class. We’re telling the container "If anyone asks for one of these, hand them this one". The second handles transient registrations "If anyone asks for one of these, make up a new one and give it to them". The third can be used to handle singletons by passing true as the sole argument "… make up a new one, and give back the same one from then on". The final Register method is what’ll save your bacon in a lot of cases. But before I can really give a good example of how, we’ll need to look at the other half of what IoC containers do, which is to hand back or "resolve" the classes you’ve registered. There are two methods here. The first is a generic method which is mainly for convenience. It makes the syntax for using the container much nicer. The real work is done by the second method which takes a type, and hands back an instance of that type.
public static TContract Resolve<TContract>() { return (TContract)Resolve(typeof(TContract)); } public static object Resolve(Type contract) { if (!dependencies.ContainsKey(contract)) throw new InvalidOperationException(string.Format("Unable to resolve type '{0}'.", contract)); if (instances.ContainsKey(contract)) return instances[contract]; var dependency = dependencies[contract]; if (dependency.DependencyType == DependencyType.Delegate) return ((Delegate)dependency.Dependency).DynamicInvoke(); var constructorInfo = ((Type)dependency.Dependency).GetConstructors() .OrderByDescending(o => (o.GetCustomAttributes(typeof(InjectionConstructorAttribute), false).Count())) .ThenByDescending(o => (o.GetParameters().Length)) .First(); var parameterInfos = constructorInfo.GetParameters(); object instance; if (parameterInfos.Length == 0) { instance = Activator.CreateInstance((Type)dependency.Dependency); } else { var parameters = new List<object>(parameterInfos.Length); foreach (ParameterInfo parameterInfo in parameterInfos) parameters.Add(Resolve(parameterInfo.ParameterType)); instance = constructorInfo.Invoke(parameters.ToArray()); } if (dependency.DependencyType == DependencyType.Singleton) instances[contract] = instance; return instance; }
I won’t explain every line individually, but would like to explain the basic flow. If you ask for any unregistered type you get an error. If an instance has been cached for a type, you get that instance. This handles both the Instance and Singleton registration cases. Delegate registrations run the delegate that was originally handed in, and return the result.
Assuming we haven’t already returned, we’ll need to actually build something, so we’ll look at the type that was registered, and try to pick its "greediest" constructor. This is the constructor that takes the most parameters. If there’s a tie, we can use the presence of the InjectionConstructorAttribute as a tie-breaker. If there’s still a tie, then all bets are off. It won’t explode, but I won’t guarantee which constructor will get called either. If you ever find yourself in this situation, then you are quite definitely doing something wrong, so you get what you deserve… there, I said it. Once we’ve picked which constructor to use, we get its parameters, and recursively call Resolve to fill them in. Finally, in the case of a singleton registration, we remember the new instance for any future requests.
Here are some examples of using the ClassRegistry in different ways. These come directly from the ClassRegistry unit tests.
[TestMethod] public void Can_resolve_registered_delegate() { string value = GetRandom.String(); ClassRegistry.Register<IFoo>(() => new Foo {Value = value}); IFoo ifoo = ClassRegistry.Resolve<IFoo>(); Assert.AreEqual(value, ifoo.Value); } [TestMethod] public void Can_resolve_registered_instance() { Bar bar = new Bar(); ClassRegistry.Register<IBar>(bar); IBar ibar = ClassRegistry.Resolve<IBar>(); Assert.AreSame(ibar, bar); } [TestMethod] public void Can_resolve_transient_type() { ClassRegistry.Register<Foo, Foo>(); Foo foo1 = ClassRegistry.Resolve<Foo>(); Foo foo2 = ClassRegistry.Resolve<Foo>(); Assert.IsInstanceOfType(foo1, typeof(Foo)); Assert.IsInstanceOfType(foo2, typeof(Foo)); Assert.IsFalse(ReferenceEquals(foo1, foo2)); } [TestMethod] public void Can_resolve_singleton_type() { ClassRegistry.Register<Bar, Bar>(true); IBar bar1 = ClassRegistry.Resolve<Bar>(); IBar bar2 = ClassRegistry.Resolve<Bar>(); Assert.IsInstanceOfType(bar1, typeof(Bar)); Assert.IsInstanceOfType(bar2, typeof(Bar)); Assert.IsTrue(ReferenceEquals(bar1, bar2)); }
Now I can explain why the "Delegate" registration type can be so useful. It all comes down to that recursive "fill in the dependency’s dependencies" part. Sometimes you just need to override the normal behavior, and the delegate registration type is what will make that possible.
Let’s assume that our example ProductRepository class requires a Linq to Sql DataContext class in order to do its work, a NorthwindDataContext specifically. Ordinarily, I would just register the NorthwindDataContext, and let the container do the rest. The trouble is that when ClassRegistry goes to look for the "greediest" constructor, it’s going to find the one that takes a connection string. Since we haven’t registered what to do when someone tries to resolve "string", nor would we want to, we’ll get an exception when we try to resolve NorthwindDataContext. Since NorthwindDataContext is a generated class, I can’t just go in and mark the correct constructor with an attribute, either. What I’ll do instead, is to tell ClassRegistry exactly what to do when someone asks for a NorthwindDataContext.
string connectionString = ConfigurationManager.ConnectionStrings["Northwind"].ConnectionString; ClassRegistry.Register<NorthwindDataContext>(() => new NorthwindDataContext(connectionString));
I could take the call to ConfigurationManager, and put the whole thing in the call to the NorthwindDataContext constructor, but that would make it part of the delegate that gets run each time we try to resolve NorthwindDataContext. Since this string won’t change over any single "run" of the service projects, that’s pretty wasteful. Instead, we’ll figure out the connection string once, outside the delegate, and use the result in the registration.
Note: You’ll notice that the ClassRegistry doesn’t have any kind of "Reset" method. That’s because in real life, you should never need one. IoC registrations aren’t the sort of thing that are supposed to change while an application is running. They’re the sort of thing you set up once when the application starts. The only place this caused me a problem was in unit testing the container itself. Since it’s a static class, once you’ve registered an instance of something, or resolved a singleton, you can never make it transient again. I got around this problem by creating two separate "test case" classes. One for instances and singletons, and one for transients. But like I said, this isn’t something that ought to come up in the real world.