Monday, October 21, 2013

Creating an IoC Container - The Container

This is the first part of a series about creating your own IoC container. Let me start by saying that there's no good reason for you to ever have to do this (unless you're stuck on .NET 1.1 and don't have generics) because there are many established open source IoC containers out there that will cater to everything the average developer will need and more; this is targeted at the inquisitive few who aren't satisfied with it just working, but need to know how.

I'm not going to go into what an IoC container is or what Dependency Injection is all about as there are many good resources that cover that already. So let's jump straight into creating the container.

Registration:

There are two major functions that an IoC container carry out and the first one is registration. This is where the container is populated with details about the types that can be requested from it.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
public void Register<T, U>()
  where U : class, new()
{
  var abstractType = typeof(T);
  var concreteType = typeof(U);

  var registration = new Registration()
  {
    AbstractType = abstractType,
    ConcreteType = concreteType
  };

  _registrations.Add(registration);
}

This method takes two generic types which define the abstract and concrete mapping for a registration. Currently, the container is unable to handle creating objects that have dependencies so this is prevented by using the
new() generic type constraint on the concrete type to ensure that it has a parameter-less constructor. After creating the Registration it is added to an internal collection.

Resolution:

The second function of an IoC container is to instantiate and serve types that are requested from it.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public T Resolve<T>()
{
  var requestedType = typeof(T);

  Registration registration = GetRegistration(requestedType);
    
  object instance = null;
  if (registration != null)
  {
    instance = Activator.CreateInstance(registration.ConcreteType);
  }

  return (T)instance;
}

private Registration GetRegistration(Type type)
{
  Registration registration = null;

  if (type.IsInterface)
  {
    registration = _registrations.Where(reg => reg.AbstractType == type)
                                 .FirstOrDefault();
  }
  else
  {
    registration = _registrations.Where(reg => reg.ConcreteType == type)
                                 .FirstOrDefault();
  }

  return registration;
}

If a registration for the requested type is found, an instance is created using its default constructor via the Activator (the Activator allows you to create objects dynamically); in the case that no registration is found in the collection of registered types, a null will be returned.

So there you have it, that's a very simple IoC container. It's not very useful in its current incarnation, but in the next part of this series, I will extend it to create instances of objects that take dependencies via their constructor. 

A complete project containing all the source code for the container and a set of unit tests can be found here.

No comments:

Post a Comment