Yet another homebrewed IoC/DI container

I recently had to work on a small library where I wanted to use inversion of control and dependency injection. I was faced an inevitable decision to make:

  • Impose a IoC container to my users (e.g. Castle Windor, Ninject, Unity)
  • Embed an IoC dll in my very little library
  • Roll my own IoC container
  • Not using IoC/DI

Let me tell you, none of these options are appealing. Imposing a certain container is just rude. So that was out of the question. Embedding bulky IoC containers in my little library dll was also ruled out because I don't like it. I bulks up the library unnecessarily without much benefit to the users.

I was not fond of rolling my own IoC container: It's that kind of thing you can easily get wrong because I usually use quite a few features from these containers. So I went about looking for people who might have hand rolled their own container when faced when facing similar constraints (a small library with no external dependency.)

I stumbled upon Oren Eini's (aka Ayende Rahien) take on the subject. A simple 15-line container for registering instances and getting them. The first, obvious problem is that it doesn't allow for dependency injection. And most hand rolled containers out there look mostly the same: A wrapper around a IDictionary<Type, object>. Well, that doesn't cut if for me. Without the dependency injection part, I lose all the benefit of testability, which is why I want a IoC/DI container in the first place!

Great. Now I have to roll my own container (note the sarcasm here.)

What I really want in a IoC/DI container

I usually don't need every fancy features in modern containers. Want I really want is:

  • The ability to register components
  • Register Singleton and Transient components
  • Providing existing instances of components
  • Register components using factory methods
  • Get or Create those components from the container

And above all,

  • Resolving constructor dependencies

Enters Dioc

Well the name kind of sucks, but it doesn't matter. What I have now is a simple 3-file code snippet, heavily commented (sorry for those who don't like green-flooded code) with all the features listed above, and understandable exceptions when something goes wrong.

More precisely, as far as component registration is concerned, the container allows you to:

  • Register interface-Implementation pairs
  • Specify either Singleton or Transient
  • Supports the registration of existing instances of components
  • Use factory methods to create components
  • Feed additional parameters on registration

The container provides the following interface for registering components:

void Singleton<T>(Hashtable parameters = null)
void Singleton<TInterface, TImplementation>(Hashtable parameters = null)
void Singleton<T>(Func<T> factoryMethod, Hashtable parameters = null)

void Transient<T>(Hashtable parameters = null)
void Transient<TInterface, TImplementation>(Hashtable parameters = null)
void Transient<T>(Func<T> factoryMethod, Hashtable parameters = null)

void Instance<T>(T instance)
void Instance<TInterface, TImplementation>(TInterface instance)

It also provides a simple interface for getting or creating those precious components. And once again, you can provide more additional parameters:

T Get<T>(Hashtable parameters = null)
object Get(Type type, Hashtable parameters = null)

Additional parameters are specified using Hashtables, in a key-value pair fashion. The keys have to match parameter names in eligible constructors.

Constructors, yes. Because the container also allows for constructor injection of dependencies and parameters. Using the container is pretty simple:

var container = new ComponentContainer();

container.Transient<ICustomerRepository, CustomerRepository>();
container.Singleton<ICustomerService, CustomerService>();

var service = container.Get<ICustomerService>();

And it works just as expected.

Cryptic exceptions?

Nope. In this 3-file micro-solution, there is an Exception created specifically for the container: ComponentResolutionException.

The container, will throw this kind of exception only (hopefully.) Plus, the container will distinguish between the following anomalies:

  • An already registered component (double-registration)
  • A non-registered component
  • The lack of an eligible constructor
  • A dependency resolution exception (exception when resolving a dependency)
  • Circular dependencies

The exception sports two main properties: An Error property, which is an enum representing one of the five aforementioned errors. And also a RootCause property which contains the initial error (when the error is a dependency resolution exception). The exception uses these properties to build a comprehensible error message, much like modern, full-blown containers do.

Where can I get it?

I purposefully did not create a GitHub repository for this. I used a GitHub Gist, instead. For those not accustomed with Gist, it's a version-controlled, git-backed, pastebin on steroids. In other words: I love it.

You can get the code here. It's free. It's obviously open source. And it's MIT licensed.

I created a Gist instead of a traditional repository because I want people to copy files or code into their solution, not a compiled dll. And a Gist does make that very easy.

You will also notice that the Gist actually contains four files, not three.

And it comes with tests!

I also included a very modest battery of tests. Tests are defined using NUnit but can easily be converted to fit your preferred runner. There are 20 tests at the moment and they cover most of the container's code.

Feel free to (read: do) include these in your solution too.

What it does not do

This is not a silver bullet. Modern IoC/DI containers are bulky for a reason: They do provide a lot of functionality this code excerpt does not. Worth mentioning are:

  • Property injection (optional dependencies, like loggers)
  • Advanced constructor resolution (this container only considers the constructor with the most parameters)
  • Lazy initializing using proxies and the likes
  • Much, much more

This post is way too long

Please, feel free to give it a try. And feel free to fork it on GitHub.

Posted by: Bryan Menard
Last revised: 14 Nov, 2011 04:52 AM History

Comments

No comments yet. Be the first!

No new comments are allowed on this post.