Andy's observations as he continues to attempt to know all that is .NET...

Wednesday, February 13, 2008

True Generic Singleton

Last weekend I was teaching Code Smarter with Design Patterns in .NET, yep that's right a weekend...The guys at Intelliflo decided they didn't have time during the regular week for training that wanted me to pack a 4 day course into 3 days over the weekend so we worked from 9 until 19:00...


 

Whilst teaching them the Singleton pattern they bought out a version they were using built on Generics.


 


public
class
AlmostSingleton<T> where T:new()

{


private
static T instance;


private
static
object initLock = new
object();


 


public
static T GetInstance()

{


if (instance == null)

{

CreateInstance();

}


 


return instance;

}


 


private
static
void CreateInstance()

{


lock (initLock)

{


if (instance == null)

{

instance = new T();

}

}

}

}


 

public
class
Highlander : AlmostSingleton<Highlander>

{


public Highlander()

{


Console.WriteLine("There can be only one...");

}

}


 

static
void Main(string[] args)

{


Highlander highlander = Highlander.GetInstance();


Highlander highlander2 = Highlander.GetInstance();


 


Debug.Assert(object.ReferenceEquals(highlander, highlander2));

}


 

A bit of a google around and it appears a few people are advocating similar solutions. On the surface all looks well, but to be really true to the singleton pattern the type which is the singleton should only ever have a single instance. This implementation requires T to have a public constructor, so that whilst it is certainly possible to always get back to a common instance via the GetInstance method, what is lacking and what the singleton pattern strictly requires is the enforcing of only a single instance of the type. Since the fact that the type has a public constructor means that another client could easily create an instance as opposed to calling GetInstance().


 

static
void Main(string[] args)

{


Highlander highlander = Highlander.GetInstance();


Highlander highlander2 = Highlander.GetInstance();


 


Debug.Assert(object.ReferenceEquals(highlander, highlander2));


 

// This will create a second instance of Highlander..

// not what we want support


 


Highlander highlander3 = new
Highlander();

}


 


 

In fact supporting the true singleton behaviour isn't too hard, it simply requires the use of a bit of reflection to create the object instance rather than rely on the typical language new construct.


 

public
class
Highlander : Singleton<Highlander>

{


private Highlander()

{


Console.WriteLine("There can be only one...");

}

}


 

public
class
Singleton<T>

{


private
static T instance;


private
static
object initLock = new
object();


 


public
static T GetInstance()

{


if (instance == null)

{

CreateInstance();

}


 


return instance;

}


 


private
static
void CreateInstance()

{


lock (initLock)

{


if (instance == null)

{


Type t = typeof(T);


 


// Ensure there are no public constructors...


 


ConstructorInfo[] ctors = t.GetConstructors();


if (ctors.Length > 0)

{


throw
new
InvalidOperationException(

String.Format("{0} has at least one accesible ctor making it impossible to enforce singleton behaviour",

t.Name));

}


 


// Create an instance via the private constructor

instance = (T)Activator.CreateInstance(t, true);

}

}

}

}


 

Now any attempt to create the instance outside the Highlander class using the new keyword would produce a compiler error, thus enforcing the true singleton pattern. I've also added a guard to make sure that when you use the Generic wrapper it ensures that you do not have a public constructor which would ultimately allow a client to by bass the singleton functionality.


 


 

About Me

My photo
Im a freelance consultant for .NET based technology. My last real job, was at Cisco System were I was a lead architect for Cisco's identity solutions. I arrived at Cisco via aquisition and prior to that worked in small startups. The startup culture is what appeals to me, and thats why I finally left Cisco after seven years.....I now filll my time through a combination of consultancy and teaching for Developmentor...and working on insane startups that nobody with an ounce of sense would look twice at...