Writing a Minimal IoC Container in C#


Coaching is fun and satisfying for me. I’ve learned that young engineers learn better by doing.

I wanted to illustrate the difference between Inversion of Control and Dependency Injection. Writing a minimal container turned out to be a great way to illustrate these concepts.

I was amazed as to how “natural” the whole thing turned out to be.

I was coaching an intern a few months ago. It’s not the first time, I find it fun to guide new people into the field. I give them shortcuts about what to learn and how to do quickly.

I talked to him about Inversion of control (IoC) and Dependency Injection (DI). I thought it would be fun to write an IoC container as small as possible. It’s easier to understand by doing it, and the making of it makes obvious why dependency injection is useful.

I had to do my research. Simple is not always easy.

Heading

Even though they are related, IoC is a broader than DI. IoC means that other code calls yours; DI goes beyond that and implements IoC by using composition.

In IoC a framework controls how modules are instantiated and managed.

DI solves problems of how an application can be independent of how its objects are created. This is very useful when you need a configurable application or when unit testing involving mock objects. DI helps you to change application more easily as it grows in size and complexity.

A DI or IoC container needs to instantiate objects (dependencies) and provide them to the application. It needs to deal with,

  • Constructor injection
  • Setter injection
  • Interface injection

The container needs to automatically instantiate objects of specific types and pass them in the constructors, setter methods, or interfaces to classes. I’ll focus on constructor injection since is the most common.

The container will support registering concrete types for interfaces and it will resolve them when requested by the user. It will auto resolve the constructor parameters as well.

An Example

I wrote an interface I called IWriter, including a Write method. That IWriter interface is implemented by a Writer concrete class. You register that in the container when initializing your application. Later, the app can request an IWriter instance to the container.

container.Register<IWriter, Writer>(); ... ... Writer writer = container.Create();

I found this post from Ayende where he wrote an IoC container in only 15 lines of code. Pretty impressive in my opinion.

His post is old and I believe that we can do better with the help of generics and reflection. So I modified Ayende’s code and I came up with an IoC container in 11 lines of code. For one thing, it doesn’t use delegates to instantiate objects.

This minimal IoC container does type checking and ensures that registered types implement the interfaces/classes that they are registered with. Generics are used to accomplish it.

It also auto-instantiates objects for the types registered. It resolves constructor parameters and auto instantiates them when resolving types.

public class MinimalContainer
{
private readonly Dictionary<Type, Type> types = new Dictionary<Type, Type>();

public void Register<TInterface, TImplementation>() where TImplementation : TInterface
{
types[typeof(TInterface)] = typeof(TImplementation);
}

public TInterface Create()
{
return (TInterface)Create(typeof(TInterface));
}

private object Create(Type type)
{
//Find a default constructor using reflection
var concreteType = types[type];
var defaultConstructor = concreteType.GetConstructors()[0];
//Verify if the default constructor requires params
var defaultParams = defaultCtor.GetParameters();
//Instantiate all constructor parameters using recursion
var parameters = defaultParams.Select(param => Create(param.ParameterType)).ToArray();

return defaultConstructor.Invoke(parameters);
}

}

This is how to use the container.

var container = new MinimalContainer();

container.Register<IWelcomer, Welcomer>();
container.Register<IWriter, ConsoleWriter>();

var welcomer = container.Create();
welcomer.SayHelloTo("World");

Here are the classes used by the example above,

public interface IWelcomer {
void SayHelloTo(string name);
}

public class Welcomer : IWelcomer
{
private IWriter writer;

public Wey(IWriter writer) {
this.writer = writer;
}

public void SayHelloTo(string name)
{
writer.Write($"Hello {name}!");
}
}

public interface IWriter {
void Write(string s);
}

public class ConsoleWriter : IWriter
{
public void Write(string s)
{
Console.WriteLine(s);
}
}

This is NOT production ready, obviously. Note that there is no config file: this container only support constructor injection and registration in the code. A complete IoC container would support way more features such other types of injections, registration in configuration files, and managing the object life-cycle.

I recommend you use full blown containers such AutoFac, Ninject or StructureMap when writing production code. I found this little task fun to do though, and it’s easier to show to less experienced developers to learn the difference between IoC and DI in as simple a way as possible.

Share this post

Table of Contents