Saturday, December 28, 2013

Creating an IoC Container - Method Chaining Syntax

This is the forth part of a series on creating an IoC container. In the third part, the container assumed responsibility for an objects lifetime by managing creating it as a singleton or not. In this part, I will delve into making the registration process more readable through the use of method chaining. This is the approach that is currently being used by the open source IoC container called StructureMap.

Method Chaining

Method chaining allows a series of methods to be called one after another. The first method call will return an object that contains the next method to be called and so on. This allows a series of actions to be carried out and for the code to be represented in a very readable manner.

Making it More Readable

This is the container's current registration syntax:

container.register<IAbstractType, ContreteType>(isSingleton: true);

From that single line of code, it is not clear that the first generic argument is the requested type and that the second is the return type. If you had never used the container before, you could very easily get them mixed up. Through using method chaining, a more readable registration syntax can be created that will look like this:

container.For<IAbstractType>().Use<ConcreteType>().AsSingleton();

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
public RegistrationExpression For<T>()
{
  var abstractType = typeof(T);

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

  _registrations.Add(registration);

  var registrationExpression = new RegistrationExpression(registration);

  return registrationExpression;
}

The For method is where the requested type is specified. The first half of the implementation is the same as the Register method, however, the Registration object is only being populated with the AbstractType before it's added to the internal collection of registrations. The final act is to create a RegistrationExpression object with a reference to the Registration.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class RegistrationExpression
{
  private readonly Registration _registration;

  public RegistrationExpression(Registration registration)
  {
    _registration = registration;
  }

  public RegistrationExpression Use<T>()
    where T : class
  {
    _registration.ConcreteType = typeof(T);

    return this;
  }

  public RegistrationExpression AsSingleton()
  {
    _registration.IsSingleton = true;

    return this;
  }
}

The RegistrationExpression object is what enables the method chaining as it contains the other behaviors relevant to completing a registration. All the methods return this (in this case, the RegistrationExpression) and it is this that allows subsequent methods to be called in a chain. These methods update the Registration through the reference stored in the private _registration variable that was supplied when the object was created.

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

Saturday, December 7, 2013

Creating an IoC Container - Singleton

This is the third part of a series on creating an IoC container. In the second part, I extended the container to handle instantiating types that received dependencies via their constructor. In this part, I will focus on how to mark a type as a singleton and how to resolve it as such.

What is a Singleton?

A singleton can be thought of as where there is only one instance of a class in existence. Every time an object is instantiated using the new keyword, it is a completely new object that is created. This means that if the same type was "newed-up" three times, there would be three separate objects living on the heap. The behavior of a singleton is the opposite to this where only one instance should ever be in existance. As an IoC container is responsible for instantiating and serving objects, it is the perfect place to manage their lifetime too.

Registration

1
2
3
4
5
6
7
public class Registration
{
  public Type AbstractType { get; set; }
  public Type ConcreteType { get; set; }
  public bool IsSingleton { get; set; }
  public object Instance { get; set; }
}

The
Registration object now has a Boolean flag to record if the registered type is to be treated as a singleton; the Instance property will be used to store a reference to the singleton when one is created.

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

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

  _registrations.Add(registration);
}

Register() now accepts an optional parameter than indicates if the type is to be a singleton or not and this is assigned accordingly when the Registration object is created.

Resolution

 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
private object Resolve(Type requestedType)
{
  var registration = GetRegistration(requestedType);

  object instance = null;
  if (registration != null)
  {
    instance = registration.Instance;

    if (instance == null)
    {
      var constructorParameters = ResolveConstructorDependencies(
        registration.ConcreteType);

      instance = Activator.CreateInstance(registration.ConcreteType, 
        constructorParameters.ToArray());

      if (registration.IsSingleton)
      {
        registration.Instance = instance;
      }
    }
  }

  return instance;
}

The requested type is instantiated in the same way as before (lines 12 through 16). A check is made to see if the type has been flagged as a singleton and if so a reference to this newly created instance is stored in the registration.Instance property before it is returned. This ties in with setting the return object to the registration.Instance (line 8) immediately after the Registration has been found and checking to see if it is null. If it is not null, it means the requested type is to be treated as a singleton and the stored reference should be returned.

In the next part of this series I will look at using method chaining to create a more readable syntax for registration.

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