15 March, 2011

How to: Create a simple typesafe IOC container

This is an example on how to create the simplest IoC Container. The purpose is understanding the value of such principle, more than it is to produce production code here. Should you decide on using this in production, a number of improvement should be added. The aim here is understanding the concept though, hence have this in mind when reading through.

In the below client example, the program has a hard reference to an implementation called “TxtLogger”. This is fine as long as the Client knows where the TxtLogger implementation is.

class Program
{
static void Main(string[] args)
{
TxtLogger log = new TxtLogger(); //hard ref. to TxtLogger!
log.Log("A message is here...");


}
}
But, what should happen, if you instead did rely on an abstraction? 

Inversion Of Control: Principle ?
Why is IoC a nice principle? Instead of referencing directly to an implementation (that might change) like the above you can rely on an abstraction instead. Where a concrete implementation resides or what implementation is made; is from the Clients perspective not important. The Client should only know about an interface called ILogger in the above case. The Inversion Of Control principle basically takes the responsibility of instantiating an implementation out of the Client class, but lets the Client class rely on abstractions instead. Basically the IoC Container works as a Factory, that produces concrete implementations of a given interface/type. But, in difference to a Factory, the IoCContainer works also as a repository that holds concrete instances. When using the IoC container, you gain this usage scenario:

class Program
{
static void Main(string[] args)
{
//container (composer)
var ioc = new IocContainer();

//register impl.
ioc.Register<ILogger>("sql", new SqlLogger());
ioc.Register<ILogger>("txt", new TxtLogger());

//get real instance...
ILogger logger = ioc.Retrieve<ILogger>("txt");

}
}

Note: The example is not all that real, as you still have a reference to TxtLogger/SqlLogger; but the focus here is how the IoC Container works internally, so please bare with me ;-)


IOC Container ?
How does it look inside? Basically an IoC Container is just a dictionary of names/types and implementations. Given the name, the client can retrieve an instance from the IoC Container (see the factory resemblance?). It is the responsibility of the IoC Container to return a concrete implementation of a type. The simplest implementation is the below IOCSplit.IocContainer class. As seen in the implementation, it has basically 2 methods:

- Register<T>
- Retrieve<T>

These two methods does what the name suggests. Register add an implementation to the dictionary (for later retrieval), whereas the Retrieve method returns a concrete implementation to the calling Client. As seen in the implementation, if you attempt to Register a concrete class and this class does not support the suggested interface, you receive an InvalidOperationException. Secondly, if you attempt to Retrieve a class that is not present in the dictionary, you receive an NotSupportedException. This (to throw appropriate exceptions); I consider good practice when developing frameworks.

namespace IOCSplif
{
/// <summary>
///
Container handling mapping of Types and Names
/// </summary>
class IocContainer
{
private readonly Dictionary<string, object> m_dictionary;

/// <summary>
///
Initializes a new instance of the <see cref="IocContainer"/> class.
/// </summary>
public IocContainer()
{
m_dictionary = new Dictionary<string, object>();
}

/// <summary>
///
Registers an implementation with the specified name.
/// </summary>
/// <param name="name">
The name.</param>
/// <param name="impl">
The type.</param>
public void Register<T>(string name, object impl)
{
if (m_dictionary.ContainsKey(name)) //don't double register
return;

//if not impl. T - then throw exceptions.
if (!typeof(T).IsAssignableFrom(impl.GetType()))
throw new InvalidOperationException("Implementation does not support Interface");

m_dictionary.Add(name, impl);
}

/// <summary>
///
Retrieves an implementation matching the specified name.
/// </summary>
/// <param name="name">
The name.</param>
/// <returns></returns>
public T Retrieve<T>(string name)
{
if(!m_dictionary.ContainsKey(name))
throw new NotSupportedException("The type is not known to the IocContainer");

               return (T) m_dictionary[name];
}
}
}

That’s really all there is to it. A number of full-fledged frameworks exists that does all this for you and to name a few you have:



  • Unity
  • MEF (some argue this is not an IOC implementation?)
  • Structure Map
  • Ninject

Technorati Tags: ,

No comments:

InRiver: Not loading your extensions?

(You really need to in the loop to appreciate the issue this post addresses). Man, I've been fighting this problem for hours before I ...