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.

No comments:

Post a Comment