I’ve already described why I had to take the plunge into the sea of implementing my own IOC container. It wasn’t becuase I didn’t like the available alternatives – I can assure you that I can never write anything that beats the proven ones already out there. If there wasn’t for these two things…
- First, since I do most of my professional activities with the .NET Compact Framework on handheld devices, I needed an IOC container that supported my main platform. Only Ninject did that at the time.
- Second, the container I choose cannot fail miserably for several of my main usage scenarios. Ninject did that.
Therefore I set out to write my own IOC container with no more bells and whistles than I needed while waiting for the Ninject bug to be fixed. I’m now using my container, the WeeContainer, in production and Ninject still has the bug. So I guess my call was appropriate – it wasn’t a case of YAGNI.
In this post I’ll look deeper into the implementation of WeeContainer to show just how little one need to do if the feature set is kept at a minimum. I’ll attack the codebase outside-in, jumping onto the public interface of WeeContainer and working my way into its not so deep core.
This is the state of WeeContainer:
public sealed class WeeContainer
{
private readonly Dictionary<Type, BindingTarget> targets = new Dictionary<Type, BindingTarget>();
private readonly Lifetime defaultLifetime;
...
}
We see that the container has a dictionary, mapping a Type to a BindingTarget. There’s also a default lifetime that will be used for all resolved dependencies unless no other lifetime is explicitly set.
The default lifetime cannot be changed after construction of the container – I see no practical benefit to allow the default lifetime to be changed halfway through the process of registering dependencies at the application composition root. At least no benefit that can keep the implementation complexity at a minimum while the client code readability is kept at a maximum.
Why is a Type mapped to a BindingTarget in the dictionary then? When a client asks the container to resolve a dependency of type T, the container is checked for an entry with the Type of T as key. The value of the dictionary entry with that key is a BindingTarget instance that defines everything that is needed to get an instance of the sought dependency.
This is how WeeContainer is constructed:
public sealed class WeeContainer
{
...
public WeeContainer()
: this(Lifetime.Transient)
{
}
public WeeContainer(Lifetime defaultLifetime)
{
Precondition.IsInRange(defaultLifetime, Lifetime.Transient, Lifetime.Singleton, "lifetime");
this.defaultLifetime = defaultLifetime;
}
...
}
All that is done during construction is setting the default lifetime. Note the use of the utility contract method Precondition.IsInRange to guarantee that the container is in a usable state after construction.
To register a dependency, one uses the first of only two public methods of WeeContainer (obviously except for the constructors):
public sealed class WeeContainer
{
...
public IBindingTarget Bind<T>()
{
return this.targets.Create(typeof(T), () => new BindingTarget(typeof(T), this.defaultLifetime));
}
...
}
The utility DictionaryExtensions.Create<TKey, TValue> method that is used above is a sibling of DictionaryExtensions.GetOrCreate<TKey, TValue>. In WeeContainer.Bind<T> a mapping between the Type of type T to a BindingTarget with the default lifetime is added. This BindingTarget is returned as an IBindingTarget.
- If the returned IBindingTarget isn’t further used by the client, then this dependency will resolve its own type, e.g., calling Bind<Foo>() will resolve an actual Foo instance with the default lifetime.
- If the dependency should map an abstract type to a concrete type, then IBindingTarget.To<T>() must be called, e.g., calling Bind<IFoo>().To<Foo>() will resolve a concrete Foo instance with the default lifetime when an IFoo instance is requested.
- If the resolved dependency shouldn’t have the default lifetime, then IBindingTarget.With<T>() or IBindingSource.With<T>() must be called. The IBindingSource instance is returned when calling IBindingTarget.To<T>().
The main purpose of the IBindingTarget and IBindingSource interfaces is to facilitate the WeeContainer fluent interface:
var container = new WeeContainer(); container.Bind<Foo>(); // Using default lifetime. container.Bind<IBar>().To<Bar>(); // Using default lifetime. container.Bind<Baz>().With(Lifetime.Singleton); // Not using default lifetime. container.Bind<IFoobar>().With(Lifetime.Singleton).To<Foobar>(); // Same effect as next line. The To/With order doesn't matter. container.Bind<IBaz>().To<Baz>().With(Lifetime.Singleton); // Same effect as previous line. The To/With order doesn't matter.
To resolve a dependency, one uses the second public method of WeeContainer:
public sealed class WeeContainer
{
...
public T Resolve<T>()
{
return (T)Resolve(typeof(T));
}
...
}
This method just delegates the work to the private method Resolve(Type):
private object Resolve(Type targetType)
{
var target = default(BindingTarget);
var targetExists = this.targets.TryGetValue(targetType, out target);
Precondition.IsTrue(targetExists, "targetExists");
return target.Singleton ?? CreateInstance(target);
}
This method first checks the contract that the requested type must have been previously registered with the container. Then a quick check to see if the found BindingTarget represents an already created dependency with singleton lifetime. If so, then it’s immediately returned, otherwise a new instance of the type defined by the BindingTarget is created in CreateInstance:
private object CreateInstance(BindingTarget target)
{
var constructors = target.Source.Type.GetConstructors();
Precondition.IsNotNullOrEmpty(constructors, "constructors");
var parameters = constructors[0].GetParameters();
var instance = parameters.Length > 0 ?
constructors[0].Invoke(parameters
.Select(parameter => Resolve(parameter.ParameterType))
.ToArray()) :
Activator.CreateInstance(target.Source.Type);
if (target.Lifetime == Lifetime.Singleton && target.Singleton == null)
{
target.Singleton = instance;
}
return instance;
}
This method actually contains most of the intelligence that can be attributed to WeeContainer and the original idea for this isn’t my own. I’ve previously mentioned that I started things off with Ken Egozi’s blog post It’s My Turn To Build An IoC Container In 15 Minutes and 33 Lines and Ayende Rahien’s prequel Building an IoC container in 15 lines of code. What I did was applying my own coding style to it, added basic lifetime management and made it use a fluent interface.
Anyway, in WeeContainer.CreateInstance I begin by using reflection on the source type to check the contract that a registered source type must be constructible, i.e., it must have at least one public constructor.
Then the first constructor is examined for its parameters, and this is a limitation of WeeContainer: Only the first found constructor is examined. This will rule out types with overloaded constructors, but since that’s something I generally don’t use, it’s not a problem for me. As stated before: the container has what I need, nothing more.
- If the constructor has no parameters, then an instance of the type is constructed using System.Activator.CreateInstance.
- If the constructor has parameters, then instances of the parameter types are recursively created by calling WeeContainer.Resolve(Type) for all of them.
When an instance has been created, it’ll be attached to a BindingTarget if its lifetime is Lifetime.Singleton so that the return statement in WeeContainer.Resolve(Type)
return target.Singleton ?? CreateInstance(target);
can be shortcut when possible. And this is really all there is to it.
All of the above, including NUnit dependencies, can be fetched from the Mercurial repository at https://joger.googlecode.com or cloned with hg clone https://joger.googlecode.com/hg/ joger.



