Simple IoC container, makes it easier to debug

by Alex Siepman 15. February 2014 17:26

Why a new IoC container?

There are already many frameworks to support Inversion of Control (IoC). Most of the frameworks use reflection and use their own code to create a new object. This makes it harder to debug your code and you have no control about how the object is created.

The solution was simple: provide a lambda expression to tell the container how the object is created. When te lambda expression is executed, you are still able to debug the creation of the object.

Advantages of this solution

  • It does not use reflection, the compiler checks the code during compile time.
  • You decide how the object is created / custom code possible.
  • Easy to debug: the creation of the object can be debugged when it is not created as you expected.
  • Easier to have multiple Containers. Just "Go to implemetation" or "Go to defintion" will bring you to the right container (like a regular object).
  • It is fast.

Disadvantages

  • When a new parameter is added to the constructor of a class, you need to add the parameter to the registration too. You can't forget this, because otherwise the code will not compile.

Register the interfaces

In this example, we have a car (Ford) with an engine that has an ignition. The constructors:

public Ford(IEngine engine)
public Engine(IIgniter igniter)
public Igniter()

Just tell the container: when I need this interface, run that Func (lambda). It can be registered this way:

var c = new Container();
c.Register<IFord>(() => new Ford(c.Use<IEngine>()));
c.Register<IEngine>(() => new Engine(c.Use<IIgniter>()));
c.Register<IIgniter>(() => new Igniter());

This is the code of the classes being injected:

public class Ford : CarBase, IFord
{
    public Ford(IEngine engine)
        : base(engine)
    {
        Brand = "Ford";
    }
}

public abstract class CarBase : ICar
{
    private readonly IEngine _engine;
    protected string Brand;

    protected CarBase(IEngine engine)
    {
        _engine = engine;
    }

    public void Start()
    {
        Console.WriteLine("Start car " + Brand);
        _engine.Start();
    }
}

public class Engine : IEngine
{
    private readonly IIgniter _igniter;

    public Engine(IIgniter igniter)
    {
        _igniter = igniter;
    }

    public void Start()
    {
        Console.WriteLine("Start engine");
        _igniter.Ignite();
    }
}

public class Igniter : IIgniter
{
    public void Ignite()
    {
        Console.WriteLine("Ignite");
    }
}

If you need to start a Ford car. This is all you need:

var ford = c.Use<IFord>();
ford.Start();

The result:

Start car Ford
Start engine
Ignite

Now what if we add an BMW...

public class BMW : CarBase, IBMW
{
    public BMW(IEngine engine)
        : base(engine)
    {
        Brand = "BMW";
    }
}

... and we need a list of all cars implemented by ICar? Just change the registration:

c.Register<IFord>(() => new Ford(c.Use<IEngine>()));
c.Register<IBMW>(() => new BMW(c.Use<IEngine>()));

c.Register<IEngine>(() => new Engine(c.Use<IIgniter>()));
c.Register<IIgniter>(() => new Igniter());

c.Register<IList<ICar>>(() => GetCarList(c));

 and implement GetCarList():

private static List<ICar> GetCarList(Container c)
{
    var cars = new List<ICar>
    {
        c.Use<IFord>(),
        c.Use<IBMW>()
    };
    return cars;
}

Starting all cars can be done this way:

var cars = c.Use<IList<ICar>>();
foreach (var car in cars)
{
    car.Start();
}

Result:

Start car Ford
Start engine
Ignite
Start car BMW
Start engine
Ignite

Creating a custom "new()"

If this is your class:

public class Product : IProduct
{
    private readonly int _index;

    public Product(int index)
    {
        _index = index;
    }

    public void ShowIndex()
    {
        Console.WriteLine("Index = " + _index);
    }
}

And you want a new index every time a new object is created. Just register it this way:

var c = new Container();     
c.Register<IProduct>(GetProduct, shared: false);

Using this method:

int _productIndex = 0;
private static Product GetProduct()
{
    _productIndex++;
    return new Product(_productIndex);
}

Now this code...

var product1 = c.Use<IProduct>();
var product2 = c.Use<IProduct>();
product1.ShowIndex();
product2.ShowIndex();

...gives this result:

Index = 1
Index = 2

If it didn't give the expected result, just put a breakpoint and you can easily see what has gone wrong:

ExceptAll()

The source code of the container

The container:

public class Container
{
    private readonly Dictionary<string, ITypeContainer> _typeContainers = 
         new Dictionary<string, ITypeContainer>();

    public void Register<T>(Func<T> createFunc, bool shared = true)
    {
        var fullTypeName = GetFullTypeName<T>();
        if (IsRegistered(fullTypeName))
        {
            throw new ContainerException<T>("The type {0} has already been registered in the container.");
        }
        var typeContainer = new TypeContainer<T>(createFunc, shared);
        _typeContainers.Add(fullTypeName, typeContainer);
    }

    public void UnRegister<T>()
    {
        var fullTypeName = GetFullTypeName<T>();
        if (!IsRegistered(fullTypeName))
        {
            throw new ContainerException<T>("The type {0} has not been registered in the container.");
        }
        _typeContainers.Remove(fullTypeName);
    }

    public T Use<T>()
    {
        ITypeContainer typeContainer;
        var fullTypeName = GetFullTypeName<T>();
        if (!_typeContainers.TryGetValue(fullTypeName, out typeContainer))
        {
            throw new ContainerException<T>("The type {0} has not been registered in the container.");
        }
        var typeContainerConcrete = (TypeContainer<T>)typeContainer;
        return typeContainerConcrete.GetObject();
    }

    public bool IsRegistered<T>()
    {
        return IsRegistered(GetFullTypeName<T>());
    }

    private bool IsRegistered(string fullName)
    {
        return _typeContainers.ContainsKey(fullName);
    }

    public void ForceCreate()
    {
        foreach (var typeContainer in _typeContainers.Values)
        {
            typeContainer.ForceCreate();
        }
    }

    private string GetFullTypeName<T>()
    {
        return typeof (T).FullName;
    }
}

The lambda needs to be stored, combined with the boolean shared value. That is stored in this class:

class TypeContainer<T> : ITypeContainer
{
    private readonly Func<T> _createFunc;
    public bool Shared { get; private set; }

    public TypeContainer(Func<T> createFunc, bool shared = true)
    {
        if (createFunc == null)
        {
            throw new ContainerException<T>("The Func to create the {0} type in de container is null.");
        }
        _createFunc = createFunc;
        if (shared)
        {
            _cache = new Lazy<T>(() => _createFunc());
            Shared = true;
        }
    }

    public T GetObject()
    {
        return Shared ? GetSharedObject() : _createFunc();
    }

    private readonly Lazy<T> _cache;
    private T GetSharedObject()
    {
        return _cache.Value;
    }

    public void ForceCreate()
    {
        GetObject();
    }
}

it implements this simple interface:

interface ITypeContainer
{
    void ForceCreate();
}

The implementation of the used exception:

public class ContainerException<T> : Exception
{
    private readonly string _message;


    public ContainerException(string message )
    {
        _message = string.Format(message, GetFullTypeName());
    }
    public override string Message
    {
        get { return _message; }
    }

    private string GetFullTypeName()
    {
        return GetFullTypeName(typeof(T));
    }

    /// <summary>
    /// Returns a string of regular and generic names
    /// in the format you would expect
    /// </summary>
    private string GetFullTypeName(Type type)
    {
        if (!type.IsGenericType)
        {
            return type.ToString();
        }

        var result = new StringBuilder();
        var parentTypeStrings = type.FullName.Split('`');

        var genericArgumentTypes = type.GetGenericArguments();
        var genericArgumentTypesFullNames = genericArgumentTypes.Select(GetFullTypeName);

        var argumentListString = new StringBuilder();
        foreach (var argumentTypeFullName in genericArgumentTypesFullNames)
        {
            if (argumentListString.Length > 0)
            {
                argumentListString.AppendFormat(", {0}", argumentTypeFullName);
            }
            else
            {
                argumentListString.Append(argumentTypeFullName);
            }
        }

        if (argumentListString.Length > 0)
        {
            result.AppendFormat("{0}<{1}>", parentTypeStrings[0], argumentListString);
        }

        return result.ToString();
    }
}

Just in case you want to run the samples yourself...

.. here are the used interfaces of the samples:

public interface IFord : ICar
{
}

public interface IBMW : ICar
{
}

public interface ICar
{
    void Start();
}

public interface IEngine
{
    void Start();
}

public interface IIgniter
{
    void Ignite();
}

public interface IProduct
{
    void ShowIndex();
}

Tags:

Generics

Add comment

  Country flag

biuquote
  • Comment
  • Preview
Loading

About the author

I am a software architect at Roxit and also a C# Developer. My main interests in the area of ​​C# are LINQ and generics

Visit my personal homepage (Dutch) for more info about me.

Month List

Page List