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.
9 comments:
Not bad, but you need to make a slight alteration to it.
declare the class as follows:
public class Singleton < T > where T : class
{
...
}
Thanks for that catch Simon. You are absolutly correct, in order to make sure some one doesn't accidently try and make a value type a singleton.
Comments from the original author of the singleton....
Good point about value types. That would be pointless. Class declaration should actually be, under that circumstance:
public abstract class Singleton< T > where T : class, new()
{
...
}
The new constraint is there to enforce the fact that a singleton should be able to be instantiated stateless and obtain its internal state in the constructor as well as the fact that you cannot create an instance of a generic with a parameterized constructor.
As you point out the downside is that the object can be instantiated on its own without the singleton. This is not all negative however due to the following:
Testing is easier as instances don't have to go through the singleton, meaning that you don't have to blow away the appdomain on successive test cases to exercise functionality on clean instances.
The incorrect usage would not escape one of my code reviews anyway :-)
Thanks Chris for that ease of testing is soemthing I hadn't considered.
So here's some initial thoughts.
If testing is a primary focus, singletons certainly create their own testing nightmare, e.g lack of ability to mock them.
However there is certainly a work around, and that would be to have a different singleton wrapper for unit testing, that has an additional method that allowed reseting of the Singleton instance.
That way you would prevent the missuse of the class in production before code review.
In the end its what ever makes you feel comfortable.
That's an interesting thought. I will have a look at that.
The more automated the review process is (or the less to review) the better.
Thanks for the suggestion.
Hi Andy! I just posted another Singleton version that does not force you to inherit Singleton. Naturally it is not exactly the same thing but it is something similar and based on your thoughts!
Generic Singleton behavior wich does not force inheritance
Cheers,
/Magnus
And I totally misspelled my link! ;~) Here's the right one!
Generic Singleton behavior which does not force inheritance
A question:
Why not use the generic version of Activator.CreateInstance?
instance = Activator.CreateInstance< T >();
instead of
instance = (T)Activator.CreateInstance(t, true);
Toke,
You can't use the generic method version as it doesn't allow you to say use private constructors. We need that behaviour since having a public constructor on the type would allow multiple creations of the object something we are trying to avoid.
Post a Comment