The Magic of IoC

I was bored…

…so I decided to design an IoC Container! This actually turned out to be a lot of fun too. I’d like to firstly state that a) there is no reason for this code, and b) this is not a replacement for established IoC solutions, such as Unity, Castle Windsor, Ninject, Authofac, StructureMap, and lastly c) this is not production code. Ah, that’s better.

The need for Inversion of Control (IoC)
Inversion of Control (IoC) is not a new concept, it’s been around for quite a while, and many will be familiar with some of the IoC containers listed above. IoC is the concept changing the behaviour of a software system to allow decoupling of components within the framework. The way I always understood IoC is that in a traditional software design, you may have a central piece of code which essentially orders other bits of code to do things specifically. I.e. “you do that, with this, and you do that, with this other bit”; in a software design that implements IoC, the central piece of code no longer orders the other bits round, rather the component requests an instance of a type so it can perform exactly what is is required to do, i.e. “can I have that, as I need to do this”.

This design pattern allows us to greatly modularise and decouple the components within our system. If we wanted to, we can rip one part out, and throw in another part. The dependant components are none the wiser. Now if you google (or bing!), you will find plenty of articles describing IoC in much fuller detail, so I won’t bore you here other than to say, IoC can benefit a software design greatly, allowing you to somewhat future-proof your code, plus increase testability and greatly segregate logical portions of code without having to worry about all that “wire-up”.

IoC Containers
IoC Containers are fabulous bits of code. They hide away the complexities of type management and they also handle dependancy injection (DI) for you. DI is the final piece of glue, it allows our containers to create our requested types without us (the developers) having to worry about explicity creating arguments for type construction (Constructor Injection), or member values (Property Injection). If you have your types registered with your favourite IoC container, then the container should automatically be able to create the appropriate types and inject them into the type being created. Let’s have a look at a quick example:

public class ConsoleLogger : ILogger
{
public ConsoleLogger(IObjectFormatter formatter)
{
// Stuff here…
}
}
In the simple code above, we have a class, the ConsoleLogger. It’s only task is write objects to the console window. Now, I’ve introduce a dependancy, an instance of IObjectFormatter that will be used to format the objects before the ConsoleLogger writes them to the output. Because this is a required dependancy, I’ve made it part of the constructor. If all goes well and I have registered an instance of IObjectFormatter with my container, when my container resolves my request for an ILogger, it should be able to create an instance of it, and inject the available IObjectFormatter into the constructor. I haven’t had to worry about any of that! Genius.

A simple IoC Container
Ok, so onto the good stuff. There wasn’t any real reason for me to design my IoC container, I just wanted to see if I could do it! It was one of those “challenge-yourself” moments, and I think I won! Now, its not a perfect design, so I’m hoping I get quite a lot of constructive criticism too.

There are 3 major parts to this IoC design; IContainer, IBuildStrategy and ILifetimeStrategy. The container is pretty obvious, but what are the other parts I hear you not ask?

IBuildStrategy
A build strategy is responsible purely for creation of types. This is where objects are built, and wars are won. Here is the interface:

/// /// Defines the required contract for implementing a build strategy. ///
public interface IBuildStrategy
{
#region Methods
/// /// Creates an instance of the specified type. ///
/// The type to create.
/// The current container.
/// An instance of the specified type.
object CreateInstance(Type type, IContainer container);
#endregion
}
It’s a simple interface, needing only the type to create, and the current container. This design supports a single build strategy per type, and bundles three pre-built strategies:

DefaultBuildStrategy – This will create an instance of a type using the default public constructor.
ConstructorBuildStrategy – This will create an instance of a type using a constructor with parameters, or a parameterless constructor that is not public. This will resolve dependancies using the current container and inject them into the required constructor.
InstanceBuildStrategy – This always returns the exact same instance each time, and is used when you register a type with a pre-built instance.
Ok, they probably could have been named better…

ILifetimeStrategy
A lifetime strategy is responsible for managing the lifetime of an instance created by the container. By default, this design ships with two strategies:

SingletonLifetimeStrategy – Manages a single isntance of a type for the lifetime of the container. Any calls always result in the same instance being returned.
AlwaysCreateLifetimeStrategy – Always creates a new instance using a build strategy for every call to resolve.
The interface is a little more developed:

/// /// Defines the required contract for implementing a lifetime strategy. ///
public interface ILifetimeStrategy : IDisposable
{
#region Methods
/// /// Resolves an instance of the specified type. ///
/// The type to resolve.
/// The current container.
/// The build strategy used to create an instance of the type.
/// A resolves instance of the specified type.
object Resolve(Type type, IContainer container, IBuildStrategy build);

/// <summary> /// Registers an object modifier with the lifetime strategy. /// </summary> /// <param name="modifier">The modifier to register.</param> void RegisterModifier(IObjectModifier modifier); #endregion
Code language: PHP (php)

}
Ignoring the RegisterModifier stuff, we can see that a lifetime strategy must resolve an instance. I didn’t call this method “Create”, because it may not be creating an instance. We pass in a build strategy for it to use if it needs to create an instance.

By combining these interfaces, we now have a mechanism for instance lifetime management and instance creation, two essential building blocks for an IoC container.

Building the container
Well, we have our under pinnings, but how does the container make use of it all? Well, lets have a look at the interface:

/// /// Defines the required contract for implementing a container. ///
public interface IContainer
{
#region Events
/// /// Fired when a component is registered with the container. ///
event EventHandler ComponentRegistered;
#endregion

#region Properties /// <summary> /// Gets the default lifetime strategy to use. /// </summary> LifetimeStrategy DefaultLifetimeStrategy { get; set; } #endregion #region Methods /// <summary> /// Gets the build strategy for the specified type. /// </summary> /// <param name="type">Gets the build strategy for the specified type.</param> /// <returns>A <see cref="IBuildStrategy"/> for the specified type.</returns> IBuildStrategy GetBuildStrategy(Type type); /// <summary> /// Gets an instance of the specified type. /// </summary> /// <typeparam name="T">The type of instance to get.</typeparam> /// <returns>An instance of the specified type, or the default if no instance can be created.</returns> T GetInstance<T>(); /// <summary> /// Gets an instance of the specified type. /// </summary> /// <param name="type">The type of instance to get.</param> /// <returns>An instance of the specified type, or the default if no instance can be created.</returns> object GetInstance(Type type); /// <summary> /// Gets all instances of the specified type. /// </summary> /// <typeparam name="T">The type of instance to get.</typeparam> /// <returns>An enumerable of available instances of the specified type.</returns> IEnumerable<T> GetInstances<T>(); /// <summary> /// Gets all instances of the specified type. /// </summary> /// <param name="type">The type of instance to get.</param> /// <returns>An enumerable of available instances of the specified type.</returns> IEnumerable<object> GetInstances(Type type); /// <summary> /// Gets the lifetime strategy for the specified type. /// </summary> /// <param name="type">The type to get a lifetime strategy for.</param> /// <returns>A <see cref="ILifetimeStrategy"/> for the specified type.</returns> ILifetimeStrategy GetLifetimeStrategy(Type type); /// <summary> /// Gets all lifetime strategies for the given type. /// </summary> /// <param name="type">The type to get lifetime strategies for.</param> /// <returns>An enumerable of lifetime strategies for the given type.</returns> IEnumerable<ILifetimeStrategy> GetLifetimeStrategies(Type type); /// <summary> /// Creates a type registration for the specified type. /// </summary> /// <typeparam name="T">The type of instance to create a registration for.</typeparam> /// <returns>An <see cref="IRegistration{T}"/> for the given type.</returns> IRegistration<T> Register<T>(); /// <summary> /// Registers a build strategy for the specified type. /// </summary> /// <param name="type">The type to register a lifetime strategy for.</param> /// <param name="strategy">The strategy used to create instances.</param> void RegisterBuildStrategy(Type type, IBuildStrategy strategy); /// <summary> /// Registers a lifetime strategy for the specified type. /// </summary> /// <param name="sourceType">The source type to register a lifetime strategy for.</param> /// <param name="targetType">The target type to register a lifetime strategy for.</param> /// <param name="strategy">The strategy used for lifetime management.</param> void RegisterLifetimeStrategy(Type sourceType, Type targetType, ILifetimeStrategy strategy); #endregion
Code language: PHP (php)

}
Ok, so we have the usual suspects…GetInstance, GetInstances, Register etc. If you’ve been exposed to other IoC containers, or even service locators, this should all be familiar. We then have methods like RegisterBuildStrategy, RegisterLifetimeStrategy. The real meat of the work for registering a type is done from the RegisterLifetimeStrategy method. It will create a binding between the source and target types, and the requested lifetime strategy.

The RegisterBuildStrategy is optional, it allows for a build strategy to be registered ahead of any types being resolved, this enables you to customise how your types are created. If no build strategy exists for a type being resolved, the target type will by analysed to figure out how it should be built and generate a build strategy for you.

How to configure
The one inescapable drawback of most IoC solutions is that it can be quite tedious to configure all the necessary components. This has been somewhat solved by approaches made by frameworks such as the Managed Extensibility Framework (MEF). MEF is all about composition and promotes type discovery ahead of configuration. I haven’t done any type discovery, so that means its back to configuration 😉

This IoC design offers two ways to configure. Either using the configuration system, or fluently (that’s the buzzword, right?) in code. The configuration method allows you to configure multiple container setups in xml, which means if you wanted, you could have multiple container setups for different scenarios. Here is a sample configuration:

<targetTypes> <add name="ConsoleLogger" sourceType="ILogger" targetType="SimpleIoC.Console.ConsoleLogger, SimpleIoC.Console" /> </targetTypes> </container> </containers>
Code language: HTML, XML (xml)



Relatively simple example, but you get the point. There are some niceties in the current version, it allows you to establish named types so you don’t have to put the full type names in each time, as well as supporting build and lifetime strategy declarations…that really needs some more work…

Getting fluent
Fluency has become a natural progression of programming design recently, and this model is no different. Let’s look at an example:

container.Register()
.Using()
.With(l => l.SomeProperty, “Hello World”)
.WithAppSetting(l => l.SomeOtherProperty, “SomeAppSettingKey”);
With fluency, we get a very expressive way of configuring individual components. There are many ways we could expand on this, but you get the idea.

Where do we go from here
Who knows really, there was no real goal here, I didn’t set out to “create one IoC container to rule them all”, it was purely an experiment to see if I could do it. And so to that end, you’ll find the Visual Studio 2010 project (.NET 4.0 target) attached to the end of this blog post. Please, I welcome any feedback, if I’ve wasted my time doing this, it’s good to know. For those interested in IoC design, this could be a starting point. It’s certainly been a learning experience for me! Peace.

SimpleIoC

Leave a Reply

Your email address will not be published.

Related Post

MVC3 and MEFMVC3 and MEF

ASP.NET MVC has been steadily maturing into a first rate web application framework. New features have been progressively enhancing the framework, changing how we how build more robust, flexible and