Also available at

Also available at my website http://tosh.me/ and on Twitter @toshafanasiev

Monday, 25 October 2010

Singleton Pattern in C#

The Singleton Pattern is a strategy that provides a single instance of a certain class for an entire application ( strictly speaking in .NET it's per Application Domain ); and ensures that any reference to an instance of that class in that application is to that single instance. This approach can help maintain consistency in multithreaded scenarios and simplify resource sharing while requiring a minimum of not-very-object-oriented static-heavy code.
This is one of the Design Patterns attributed to the Gang of Four.
This pattern is typically implemented using lazy instantiation - only creating the instance the first time it is referenced - which can be achieved using double-checked locking to minimise the performance overhead of thread synchronisation, as follows:
sealed class SingletonOne {
  private SingletonOne() {
    // constructor logic
  }
  private static SingletonOne s_instance;
  private static object s_padlock = new Object();
  public static SingletonOne Instance {
    get {
      if ( s_instance == null ) {
        lock( s_padlock ) {
          if ( s_instance == null ) {
            s_instance = new SingletonOne(); 
          }
        }
      }
      return s_instance;
    }
  }
}

There is nothing actually wrong with this ( as opposed to the examples you see that implement
no thread-synchronisation at all ) but it is more handmade and marginally less efficient than the following implementation which takes advantage of some built-in CLR features:
sealed class SingletonTwo {
  private SingletonTwo() {
    // constructor logic
  }
  public static readonly SingletonTwo Instance = new SingletonTwo();
}

What this second implementation gives you is a single instance that is thread-safely single ( ensured by the CLR's initialisation of static fields and enforcement of the readonly attribute ) but without the overhead of the null check or even the accessor call - this field access is about 20 times faster than the above property access ( see complete program at the end of the post ); as well as far less code to write, maintain, curse etc.
One thing to be aware of, however, is the timing of this new initialisation scheme. Disassembling the program with ILDasm.exe shows the class declaration as follows:
.class private auto ansi sealed beforefieldinit SingletonTwo
  extends [mscorlib]System.Object

The point of interest here being the beforefieldinit attribute - this tells the CLR to ensure that the field is initialised at some point before the field is accessed; in other words, it could occur quite a while before - at any point when the CLR deems appropriate based on factors such as CPU load ( there's a very good article on this here: http://csharpindepth.com/Articles/General/Beforefieldinit.aspx ).
If the timing of a singleton's initialisation code is important and it should run just before first access, not at some arbitrary point earlier on; this can be achieved by instructing the compiler to omit the beforefieldinit attribute, which in C# is as simple as defining a class constructor ( sometimes called a static constructor ). The following class definition shows this in action:
sealed class SingletonThree {
  private SingletonThree() {
    // constructor logic
  }
  public static readonly SingletonThree Instance;
  static SingletonThree() {
    Instance = new SingletonThree();
  }
}

This class definition disassembles to the following:
.class private auto ansi sealed SingletonThree
  extends [mscorlib]System.Object

( note the lack of beforefieldinit; note also that it's irrelevant what the class constructor actually does - its presence signals the compiler to omit that attribute )
So all in all: cleaner, quicker code and control over timing - what's not to like?
By the way, for my money the best reference of on MSIL internals such as beforefieldinit is Inside Microsoft .NET IL Assembler by Serge Lidin, the guy behind IL itself.

Below is a complete program that includes the performance testing from which I arrived at the property access vs. field access figures:
using System;
using System.Diagnostics;

sealed class SingletonOne {
  private SingletonOne() {
    // constructor logic
  }
  private static SingletonOne s_instance;
  private static object s_padlock = new Object();
  public static SingletonOne Instance {
    get {
      if ( s_instance == null ) {
        lock( s_padlock ) {
          if ( s_instance == null ) {
            s_instance = new SingletonOne(); 
          }
        }
      }
      return s_instance;
    }
  }
}

sealed class SingletonTwo {
  private SingletonTwo() {
    // constructor logic
  }
  public static readonly SingletonTwo Instance = new SingletonTwo();
}

sealed class SingletonThree {
  private SingletonThree() {
    // constructor logic
  }
  public static readonly SingletonThree Instance;
  static SingletonThree() {
    Instance = new SingletonThree();
  }
}

class test {
  static void Main() {
    Console.WriteLine(
      "Testing SingletonOne: {0}", SingletonOne.Instance == SingletonOne.Instance
    );
    Console.WriteLine(
      "Testing SingletonTwo: {0}", SingletonTwo.Instance == SingletonTwo.Instance
    );
    Console.WriteLine(
      "Testing SingletonThree: {0}", SingletonThree.Instance == SingletonThree.Instance
    );

    // performance testing
    const int ITERATIONS = 50000000;

    // test property access
    GC.Collect();
    GC.WaitForPendingFinalizers();
    Stopwatch timerOne = Stopwatch.StartNew();
    for ( int i=0; i<ITERATIONS; i++ ) {
      SingletonOne s = SingletonOne.Instance;
    }
    timerOne.Stop();

    // test field access
    GC.Collect();
    GC.WaitForPendingFinalizers();
    Stopwatch timerTwo = Stopwatch.StartNew();
    for ( int i=0; i<ITERATIONS; i++ ) {
      SingletonTwo s = SingletonTwo.Instance;
    }
    timerTwo.Stop();

    Console.WriteLine(
      "Property access: {0} ms", timerOne.ElapsedMilliseconds / ( double ) ITERATIONS
    );
    Console.WriteLine(
      "Field access: {0} ms", timerTwo.ElapsedMilliseconds / ( double ) ITERATIONS
    );
  }
}

No comments:

Post a Comment