Poor Mans Delegate IOC container

I have been doing some unit testing and mocking in a new application I am working on and I have come to appreciate the need for an IOC Container. I know there are several well established IOC Container frameworks out there (http://csharp-source.net/open-source/containers) but it seemed like such a simple idea I decided to make my own. I created a very very simple Container using lambda expressions and Func<> delegates.

public static class Container
{
    private static Dictionary<Type, Delegate> _registrations
        = newDictionary<Type, Delegate>();

    public static void Initialize()
    {
        _registrations.Clear();
    }

    public static void Register<TResult>(Func<TResult> create)
    {
        Register(typeof(TResult), create);
    }

    public static void Register<T, TResult>(Func<T, TResult> create)
    {
        Register(typeof(TResult), create);
    }

    public static void Register<T1, T2, TResult>(Func<T1, T2, TResult> create)
    {
        Register(typeof(TResult), create);
    }

    public static void Register(Type type, Delegate create)
    {
        if (_registrations.ContainsKey(type))
            throw new InvalidOperationException("Same key registered twice");

        _registrations.Add(type, create);
    }

    public static T Create<T>(paramsobject[] args)
    {
        Type key = typeof(T);
        if (!_registrations.ContainsKey(key))
            throw new InvalidOperationException("Key not registered in container");

        return (T)_registrations[typeof(T)].DynamicInvoke(args);
    }
}

So there are really three methods, Initialize, Create and Register. The last register is the most important the rest are simple helpers.

So imagine in your application you had the following dependency situation:

public class Foo
{
    IBar _bar;
    public Foo()
    {
        _bar = new Bar(this);
    }
}
 
public class Bar
{
    public Bar(IFoo foo) { }
}
Here we are directly instantiating some specific type inside of another type, thus creating a dependency. One way to break this dependency is to introduce Interfaces and an IOC container to use as the factory with which you create your types. For example:
public interface IFoo { }

public interface IBar { }

public class Foo : IFoo
{
    IBar _bar;
    public Foo()
    {
        _bar = Container.Create<IBar>(this);
    }
}

public class Bar : IBar
{
    public Bar(IFoo foo) { }
}

Instead of directly instantiating Bar we are now using the container to create an IBar for us. So now all we have to do is register a type with the container at either application initialization or in the test Setup method. For example:

Container.Register<IFoo>(() => { returnnewFoo(); });
Container.Register<IFoo, IBar>((IFoo f) => { returnnewBar(f); });
IFoo foo = Container.Create<IFoo>();

Calling Create on the Container for IFoo will result in calling the IFoo registered lambda expression which will result in another call to the IBar registration. This breaks the dependencies so we can effectively test the Foo class now. Here is an example of how you might mock the Bar class using Rhino mocks:

IBar mockBar = MockRepository.GenerateMock<IBar>();
Container.Register<IFoo, IBar>((IFoo f) => { return mockBar; });
IFoo foo = newFoo(); 

We are generating a mock IBar then registering it in the container. Now when we call the constructor on Foo it will use the container to create an IBar which will pass it the mock object we created. From here we can setup expectations and verify all values on foo as normal without having to worry about side effects.

This is so powerful to me that it’s almost worth saying that I believe a unit test should only test a single instance in your application per test. In this application I am working on this is my goal and I am starting to see the benefits of such an approach.
If you do not break your dependencies then your unit tests tend to tell you something valuable when they pass but tend to not tell you anything valuable when they fail. If you find yourself running a unit test suite and feeling that it is ok if a few tests fail then it may be time to start breaking up your dependencies.

Author: justinmchase

I'm a Software Developer from Minnesota.

Leave a Reply

%d bloggers like this: