Click here to Skip to main content
11,481,404 members (62,425 online)
Click here to Skip to main content

An IoC Container in 15 min

, 3 Jun 2011 CPOL 42.3K 676 97
Rate this:
Please Sign up or sign in to vote.
How to create a very simple and very basic IoC container from scratch

Introduction

Last week, I was programming a Compact Framework application. I'm so used to IoC right now that I hardly see myself programming in another way. The problem is that Windsor (my favorite) container doesn't work on CF. So I looked at my options. It turned out there weren't many. Actually I found some. One of them: Ninject, although very promising implied some study, because it ain't a container, and I didn't get the time then. There were another couple that worked pretty similar to Windsor, but they had problems resolving generic components. So I thought: I want my own!!!

Changes

  1. 18-Jun-2011: The IService interface wasn't that useful, if at all. So, every use of it has been replaced with object. For the reader who sees this article for the first time: never mind.
  2. 18-Jun-2011: Generics registration improved, there were some cases causing problems, they are not anymore.
  3. 15-May-2012: References on IoC and DI added. 

Building an IoC from Scratch

Let's start with the interfaces:

public interface IServicesContainer 
{ 
	TService Resolve<TService>(); 
	object Resolve(Type tService); 
}

public interface IServicesRegistrar : IServicesContainer
{
	void RegisterForAll(params Type[] tServices);
	void RegisterForAll(IEnumerable<Type> tServices);

	void RegisterFor(Type tService, params Type[] tInterfaces);
	void RegisterFor(Type tService, params IEnumerable<Type> tInterfaces);
}
Code Excerpt 1: IoC interfaces

Everything we might need is there. The actual container IServicesContainer, and a registrar IServicesRegistrar. A container should implement both IServicesContainer and IServicesRegistrar, but the registrar is only needed when registering so later on we could use only the container itself.

We will need a helper type for storing information about types and instances:

internal class ServiceDescriptor 
{ 
	public Type ServiceType { get; set; } 
	public object Instance { get; set; } 
}
Code Excerpt 2: ServiceDescriptor

Let's start with the container:

internal class ServicesContainer : IServicesRegistrar 
{ 
	private readonly IDictionary<Type, ServiceDescriptor> _services = 
		new Dictionary<Type, ServiceDescriptor>(); 
	
	public TService Resolve<TService>() 
	{ 
		return (TService)Resolve(typeof(TService)); 
	}  
	
	public object Resolve(Type tService) 
	{ 
		return GetInstance(tService);
	}
	...
}
Code Excerpt 3: The Container, Most Of It

A dictionary would hold the mappings. GetInstance would find the existing instance or create a new one. Let's continue with GetInstance:

internal class ServicesContainer : IServicesRegistrar 
{
	...
	private object GetInstance(Type tService)
	{
		if (_services.ContainsKey(tService)) 
			return GetInstance(_services[tService]); 

		var genericDefinition = tService.GetGenericTypeDefinition(); 
		if (_services.ContainsKey(genericDefinition)) 
			return GetGenericInstance(tService, 
				_services[genericDefinition].ServiceType); 

		throw new Exception("Type not registered" + tService);
	}

	...
}
Code Excerpt 4: GetInstance

There are three cases:

  1. The type is known, if so, we ask for GetInstance but this time with the ServiceDescriptor as parameter, this overload will be next.
  2. The type is unknown, but maybe it is a generic type, and its generic type definition is known, if so, we ask GetGenericInstance to solve the problem.
  3. The type is unknown, you've seen this movie so there is no need to tell you what happens next.
internal class ServicesContainer : IServicesRegistrar 
{
	...
	private object GetInstance(ServiceDescriptor serviceDescriptor) 
	{ 
		return serviceDescriptor.Instance ?? ( serviceDescriptor.Instance = 
			CreateInstance(serviceDescriptor.ServiceType)); 
	}
}
Code Excerpt 5: GetInstance II

Not much here, just resolved an interface to a concrete class, then asked CreateInstance to instantiate that class. The really interesting stuff happens in CreateInstance, that's why it will have to wait. Let's look at GetGenericInstance first:

internal class ServicesContainer : IServicesRegistar
{
	...
	private object GetGenericInstance(Type tService, Type genericDefinition) 
	{ 
		var genericArguments = tService.GetGenericArguments(); 
		var actualType = genericDefinition.MakeGenericType(genericArguments); 
		var result = CreateInstance(actualType); 

		_services[tService] = new ServiceDescriptor 
		{ 
			ServiceType = actualType, 
			Instance = result 
		}; 

		return result; 
	}
}
Code Excerpt 6: GetGenericInstance

There generic arguments are taken from actual requested type tService and a new generic type is created from the registered type and these arguments. Then we request CreateInstance to help us out. Think it's time:

internal class ServicesContainer : IServicesRegistrar
{
	...
	private object CreateInstance(Type serviceType) 
	{ 
		var ctor = serviceType.GetConstructors().First(); 
		var dependecies = ctor.GetParameters()
			.Select(p => Resolve(p.ParameterType)).ToArray(); 

		return (IService)ctor.Invoke(dependecies); 
	}
}
Code Excerpt 7: CreateInstance

First we get a constructor, there should be only one. Then we get the constructor parameter types (dependencies) and resolve them. Finally, we create and return the instance. That's it, we just need to register types and our IoC container would be ready.

internal class ServicesContainer : IServicesRegistrar
{
	...
	public ITypeRegistrar RegisterForAll(params Type[] implementations) 
	{ 
		return Register((IEnumerable<Type>)implementations); 
	} 

	public ITypeRegistrar RegisterForAll(IEnumerable<Type> implementations) 
	{ 
		foreach (var impl in implementations) 
			RegisterFor(impl, impl.GetInterfaces()); 

		return this; 
	} 

	public ITypeRegistrar RegisterFor(Type implementation, params Type[] interfaces) 
	{ 
		return RegisterFor(implementation, (IEnumerable<type>)interfaces); 
	} 

	public ITypeRegistrar RegisterFor
		(Type implementation, IEnumerable<type> interfaces) 
	{ 
		foreach (var @interface in interfaces) 
			_services[GetRegistrableType(@interface)] = 
				new ServiceDescriptor 
				{ 
					ServiceType = implementation 
				}; 

		return this; 
	} 

	private static Type GetRegistrableType(Type type) 
	{ 
		return type.IsGenericType && type.ContainsGeneric ? 
				type.GetGenericTypeDefinition() : type; 
	}
}
Code Excerpt 8: Registrar Members

As you can see, there are a couple of changes. ITypeRegistrar is just a base for IServicesRegistrar, and all void members have been replaced with ITypeRegistrar to allow method chaining.

Generics Registration

Registering generics has 2 cases which had been solved by method GetRegistrableType. Next section will show an example of each. Let's see the situations:

  1. Register an open constructed type (generic arguments not specified). Let's call this one an "implicit registration".
  2. Register a close constructed type (generic arguments have been specified). Let's call this one an "explicit registration".

Let's take a look at both cases in the simplest way:

public interface ITypeNamePrinter<TType>
{
	void Print();
}

public class TypeNamePrinter<TType> : ITypeNamePrinter<TType>
{
	public void Print()
	{
		Console.WriteLine(typeof(TType).FullName);
	}
}

[TestFixture]
[TestClass]
public class GenericRegistrations
{
	[Test]
	[TestMethod]
	public void ExplicitRegistration()
	{
		var services = new ServicesContainer();

		services.RegisterForAll(typeof(TypeNamePrinter<int>),
			typeof(TypeNamePrinter<string>), typeof(TypeNamePrinter<Type>));

		services.Resolve<ITypeNamePrinter<int>>().Print();
		services.Resolve<ITypeNamePrinter<string>>().Print();
		services.Resolve<ITypeNamePrinter<Type>>().Print();

		Assert.Throws(typeof(Exception), () => 
			services.Resolve<ITypeNamePrinter<float>>().Print());
	}

	[Test]
	[TestMethod]
	public void ImplicitRegistration()
	{
		var services = new ServicesContainer();

		services.RegisterForAll(typeof(TypeNamePrinter<>));

		services.Resolve<ITypeNamePrinter<int>>().Print();
		services.Resolve<ITypeNamePrinter<string>>().Print();
		services.Resolve<ITypeNamePrinter<Type>>().Print();

		Assert.DoesNotThrow(() => 
			services.Resolve<ITypeNamePrinter<float>>().Print());
	}
}
Code Excerpt 9: Generics Registrations

Explicit implementation sample registers every type. When asking for one not explicitly registered, an exception is thrown. Implicit registration simply registers the open constructed type and every time you ask the container for a new close constructed type's instance, it creates the registration and the instance for that type. Using combinations of implicit and explicit could result in a very flexible an interesting scenario. To end with this subject, let's look at one, a little more complicated, example with explicit registrations.

public interface IExplicit<TType>
{
	void Print();
}

public class StringExplicit : IExplicit<string>
{
	public void Print()
	{
		Console.WriteLine("System.String");
	}
}

public class IntExplicit : IExplicit<int>
{
	public void Print()
	{
		Console.WriteLine("System.Int32");
	}
}

public class TypeExplicit : IExplicit<Type>
{
	public void Print()
	{
		Console.WriteLine("System.Type");
	}
}

[TestFixture]
[TestClass]
public class ExplicitRegistrations
{
	[Test]
	[TestMethod]
	public void Test()
	{
		var services = new ServicesContainer();
		services.RegisterForAll(typeof(StringExplicit), 
			typeof(IntExplicit), typeof(TypeExplicit));

		services.Resolve<IExplicit<int>>().Print();
		services.Resolve<IExplicit<string>>().Print();
		services.Resolve<IExplicit<Type>>().Print();
	}
}
Code Excerpt 10: Explicit registrations

IoC Kingdom

Our new IoC Container is ready. Let's play with it:

    public interface IKing : IService
    {
        IBoss<IGuard> Captain { get; }
        IBoss<IMaid> Mistress { get; }

        void RuleTheCastle();
    }

    public interface IServant : IService
    {
        void Serve();
    }

    public interface IGuard : IServant
    {

    }

    public interface IMaid : IServant
    {

    }

    public interface IBoss<TServant>
        where TServant : IServant
    {
        TServant Servant { get; }
        void OrderServantToServe();
    }

    public class King : Service, IKing
    {
        public King(IBoss<IGuard> captain, IBoss<IMaid> mistress)
        {
            Captain = captain;
            Mistress = mistress;
        }

        public void RuleTheCastle()
        {
            Console.WriteLine("Rule!!!");

            Captain.OrderServantToServe();
            Mistress.OrderServantToServe();
        }

        public IBoss<IGuard> Captain
        {
            get;
            private set;
        }

        public IBoss<IMaid> Mistress
        {
            get;
            private set;
        }
    }

    public class Boss<TServant> : Service, IBoss<TServant>
        where TServant : IServant
    {
        public Boss(TServant servant)
        {
            Servant = servant;
        }

        public TServant Servant
        {
            get;
            private set;
        }

        public void OrderServantToServe()
        {
            Servant.Serve();
        }
    }

    public class Guard : Service, IGuard
    {
        public void Serve()
        {
            Console.WriteLine("Watch!!");
        }
    }

    public class Maid : Service, IMaid
    {
        public void Serve()
        {
            Console.WriteLine("Clean!!");
        }
    }
Code Excerpt 11: The King and the Castle

A test project has been included along with the solution, it contains this sample, which is a dummy but has everything we need for a test. A couple of types useful for registration are also included, but they are too lame to be explained here.

Before saying good bye, let's see how to use the container.

var services = new ServicesContainer(); 
services.RegisterForAll(ServicesImplementation .FromAssemblyContaining<IKing>());

var king = services.Resolve<IKing>();
king.RuleTheCastle();
Code Excerpt 12: Using the Container

I hope it will be useful!!! Enjoy!!

References 

  1. Martin Fowler, Inversion of Control Containers and the Dependency Injection pattern 
  2. Wikipedia, Inversion of control 

 

 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Erich Ledesma
Architect SunHotels
Spain Spain
I Received a Bachelor's Degree in Computer Science at the Mathematics and Computer Science Faculty, University of Havana, Cuba.

I mainly work in web applications using C# and some Javascript. Some very few times do some Java.

Comments and Discussions

 
GeneralMy vote of 5 Pin
Artem Elkin21-Oct-13 12:11
professionalArtem Elkin21-Oct-13 12:11 
GeneralMy vote of 2 Pin
sathishvenkat25-Aug-13 21:10
membersathishvenkat25-Aug-13 21:10 
GeneralMy vote of 5 Pin
Kanasz Robert13-Apr-13 6:42
mvpKanasz Robert13-Apr-13 6:42 
QuestionDiscussion for IOC was required Pin
Tridip Bhattacharjee14-May-12 21:58
memberTridip Bhattacharjee14-May-12 21:58 
AnswerRe: Discussion for IOC was required Pin
Erich Ledesma14-May-12 22:17
memberErich Ledesma14-May-12 22:17 
QuestionExcelente Pin
craliaga29-Mar-12 11:25
membercraliaga29-Mar-12 11:25 
AnswerRe: Excelente Pin
Erich Ledesma29-Mar-12 11:37
memberErich Ledesma29-Mar-12 11:37 
Questionnice Pin
CIDev15-Jul-11 7:11
memberCIDev15-Jul-11 7:11 
GeneralMy vote of 5 Pin
L Hills11-Jul-11 1:07
memberL Hills11-Jul-11 1:07 
GeneralRe: My vote of 5 Pin
Erich Ledesma12-Jul-11 23:17
memberErich Ledesma12-Jul-11 23:17 
GeneralMy vote of 5 Pin
Rhuros11-Jul-11 0:48
memberRhuros11-Jul-11 0:48 
GeneralRe: My vote of 5 Pin
Erich Ledesma12-Jul-11 23:16
memberErich Ledesma12-Jul-11 23:16 
QuestionHave a look at LightCore Pin
Daniel M. Camenzind9-Jul-11 0:20
memberDaniel M. Camenzind9-Jul-11 0:20 
AnswerRe: Have a look at LightCore Pin
Erich Ledesma12-Jul-11 23:16
memberErich Ledesma12-Jul-11 23:16 
GeneralGood article ... is MEF available in CF Pin
Shivprasad koirala8-Jun-11 17:32
mvpShivprasad koirala8-Jun-11 17:32 
GeneralRe: Good article ... is MEF available in CF [modified] Pin
Erich Ledesma8-Jun-11 21:21
memberErich Ledesma8-Jun-11 21:21 
GeneralRe: Good article ... is MEF available in CF Pin
Shivprasad koirala9-Jun-11 0:01
mvpShivprasad koirala9-Jun-11 0:01 
GeneralRe: Good article ... is MEF available in CF Pin
Adil Mughal10-Jun-11 20:25
memberAdil Mughal10-Jun-11 20:25 
GeneralRe: Good article ... is MEF available in CF Pin
Shivprasad koirala11-Jun-11 0:31
mvpShivprasad koirala11-Jun-11 0:31 
GeneralRe: Good article ... is MEF available in CF Pin
Erich Ledesma13-Jun-11 1:26
memberErich Ledesma13-Jun-11 1:26 
GeneralRe: Good article ... is MEF available in CF Pin
Rafael Nicoletti30-Aug-13 14:16
memberRafael Nicoletti30-Aug-13 14:16 
GeneralInteresting Pin
CIDev8-Jun-11 9:52
memberCIDev8-Jun-11 9:52 
GeneralNice Work Pin
Jammer8-Jun-11 5:44
memberJammer8-Jun-11 5:44 
GeneralRe: Nice Work Pin
Erich Ledesma8-Jun-11 5:55
memberErich Ledesma8-Jun-11 5:55 
GeneralLoving it Pin
Mel Padden7-Jun-11 2:48
memberMel Padden7-Jun-11 2:48 
I'm a big fan of this kind of thing; people who understand the reasoning behind the use of a particular pattern or framework, go off and create a lightweight implementation of only the parts that they need, and use that. I've done this kind of thing a few times myself. Makes development a pleasure.
Smokie, this is not 'Nam. This is bowling. There are rules.
www.geticeberg.com

http://melpadden.wordpress.com

GeneralGood stuff Pin
Sacha Barber7-Jun-11 1:51
mvpSacha Barber7-Jun-11 1:51 
GeneralAnother take on the "no IoC container for .NET CF" issue: WeeContainer Pin
Johann Gerell6-Jun-11 22:51
memberJohann Gerell6-Jun-11 22:51 
GeneralRe: Another take on the "no IoC container for .NET CF" issue: WeeContainer Pin
Erich Ledesma7-Jun-11 0:47
memberErich Ledesma7-Jun-11 0:47 
GeneralAnother IOC framework - OpenNETCF.IoC [modified] Pin
José Joye6-Jun-11 21:01
memberJosé Joye6-Jun-11 21:01 
GeneralRe: Another IOC framework - OpenNETCF.IoC Pin
Erich Ledesma6-Jun-11 23:02
memberErich Ledesma6-Jun-11 23:02 
GeneralMy vote of 5 Pin
aprishchepov6-Jun-11 20:00
memberaprishchepov6-Jun-11 20:00 
GeneralMy vote of 5 Pin
Marcelo Ricardo de Oliveira6-Jun-11 14:08
mvpMarcelo Ricardo de Oliveira6-Jun-11 14:08 
GeneralRe: My vote of 5 Pin
Erich Ledesma6-Jun-11 21:32
memberErich Ledesma6-Jun-11 21:32 
GeneralThanks for this Pin
mechamonkey5-Jun-11 23:04
membermechamonkey5-Jun-11 23:04 
GeneralRe: Thanks for this Pin
Erich Ledesma5-Jun-11 23:21
memberErich Ledesma5-Jun-11 23:21 
GeneralMy vote of 5 Pin
Patrick Kalkman3-Jun-11 22:05
memberPatrick Kalkman3-Jun-11 22:05 
GeneralNow go download a mature IOC container Pin
Ramon Smits3-Jun-11 7:30
memberRamon Smits3-Jun-11 7:30 
GeneralRe: Now go download a mature IOC container Pin
vbfengshui3-Jun-11 8:19
membervbfengshui3-Jun-11 8:19 
GeneralRe: Now go download a mature IOC container Pin
Erich Ledesma3-Jun-11 9:10
memberErich Ledesma3-Jun-11 9:10 
GeneralRe: Now go download a mature IOC container Pin
Erich Ledesma3-Jun-11 9:04
memberErich Ledesma3-Jun-11 9:04 
GeneralRe: Now go download a mature IOC container Pin
José Joye7-Jun-11 2:25
memberJosé Joye7-Jun-11 2:25 
GeneralRe: Now go download a mature IOC container Pin
Erich Ledesma7-Jun-11 2:29
memberErich Ledesma7-Jun-11 2:29 
GeneralRe: Now go download a mature IOC container Pin
Johann Gerell6-Jun-11 22:46
memberJohann Gerell6-Jun-11 22:46 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.150520.1 | Last Updated 3 Jun 2011
Article Copyright 2011 by Erich Ledesma
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid