An IoC Container in 15 min
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!!!
Building an IoC from Scratch
Let's start with the interfaces:
public interface IService
{
IServiceContainer Services { get; set; }
}
public interface IServicesContainer
{
TService Resolve<TService>() where TService : IService;
IService 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);
}
Everything we might need is there. A service base IService
, 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 IService Instance { get; set; }
}
Let's start with the container:
internal class ServicesContainer : IServicesRegistrar
{
private readonly IDictionary<Type, ServiceDescriptor> _services =
new Dictionary<Type, ServiceDescriptor>();
public TService Resolve<TService>() where TService : IService
{
return (TService)Resolve(typeof(TService));
}
public IService Resolve(Type tService)
{
var result = GetInstance(tService);
result.Services = this;
return result;
}
...
}
A dictionary would hold the mappings. GetInstance
would find the existing instance or create a new one. The container is set into the service Services
property, just in case. Let's continue with GetInstance
:
internal class ServicesContainer : IServicesRegistrar
{
...
private IService 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);
}
...
}
There are three cases:
- When the type is known, if so, we ask for
GetInstance
but know with theServiceDescriptor
as parameter, this overload will be next. - 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. - 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 IService GetInstance(ServiceDescriptor serviceDescriptor)
{
return serviceDescriptor.Instance ?? ( serviceDescriptor.Instance =
CreateInstance(serviceDescriptor.ServiceType));
}
}
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 IService 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;
}
}
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 IService CreateInstance(Type serviceType)
{
var ctor = serviceType.GetConstructors().First();
var dependecies = ctor.GetParameters()
.Select(p => Resolve(p.ParameterType)).ToArray();
return (IService)ctor.Invoke(dependecies);
}
}
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.GetGenericTypeDefinition() : type;
}
}
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. 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("I 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!!");
}
}
A test project has been included along with the solution, it contains this sample, which is 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. So let's do it.
var services = new ServicesContainer();
services.RegisterForAll(ServicesImplementation .FromAssemblyContaining<IKing>());
var king = services.Resolve<IKing>();
king.RuleTheCastle();
Hope it will be useful!!! Enjoy!!
Post Comment
IbG3hk Really enjoyed this article post. Really Great.