Click here to Skip to main content
15,867,453 members
Articles / Web Development / HTML

Design Flaws of the Singleton Pattern in C#

Rate me:
Please Sign up or sign in to vote.
2.92/5 (16 votes)
2 Sep 2014LGPL33 min read 52.5K   279   38   20
How to create a generic singleton for .NET.

The Paradigm

Consider this common Singleton pattern implementation:

C#
public class Singleton
{
  static private readonly object locker = new object();

  static public Singleton Instance
  {
    get
    {
       lock ( locker ) 
       {
         if ( _Instance == null ) _Instance = new Singleton();
         return _Instance;
       }
    }
  }
  static private volatile Singleton _Instance;

  private Singleton()
  {
  }
}

The problem is that you can inherit this class and create a public constructor if there is no private constructor. Furthermore, static members are allowed. This is no longer a singleton at all. Setting the class as sealed can be an acceptable solution, but you must implement singleton by singleton, i.e., more than ten lines. Thus, coding such a singleton can be the source of many errors and difficulties. Thinking with factoring is not only an agile principle, it is a mathematical theorem.

Defining a Generic Singleton

A generic solution is to check the absence of static members and that there is only one parameterless private constructor, or an exception is thrown. Singletons that inherit this class can't be inherited and must be sealed. Moreover, the implementation of singleton types are checked at program startup. Therefore, it is not the best solution, but the only thing to do is to create one parameterless private constructor, no static members, and seal the class.

Here are the members of the proposed singleton:

C#
abstract public class Singleton<T> where T : Singleton<T>;

This is the declaration of a generic abstract class where T is a singleton. By writing this, the type consistency is clear.

C#
static public string Filename; 
static public void Save(); 

This is used to provide storage on disk for persistent singletons and to save their states.

C#
static public T Instance; 
static public T GetInstance();

This is the classic access to the instance of the singleton.

C#
static public T GetPersistentInstance(string filename);
static public T GetPersistentInstance();

This creates a persistent instance: it deserializes the object from the disk or creates a new one. It uses a specific filename or a system name. Note: Defining the name after using the singleton doesn't load a new instance, and should throw an error if localization exists.

C#
static private T CreateInstance(); 
static internal ConstructorInfo CheckImplementation();

This creates the instance by invoking the default private constructor. The singleton implementation validity is checked as indicated above.

Here are the Serialize and Deserialize functions:

C#
static public void Serialize(this object obj, string filename, int buffersize)
{
  if ( !obj.GetType().IsSerializable )
    throw new IOException(SystemManager.Language.Get("ObjectIsNotSerializable",
                          obj.GetType().Name));
  using ( FileStream f = new FileStream(filename,
                                        FileMode.Create, FileAccess.Write, 
                                                         FileShare.None,
                                        buffersize
                                        ) )
    new BinaryFormatter().Serialize(f, obj);
}

static public object Deserialize(this string filename, int buffersize)
{
  using ( FileStream f = new FileStream(filename,
                                        FileMode.Open, FileAccess.Read, 
                                                       FileShare.None,
                                        buffersize) )
    return new BinaryFormatter().Deserialize(f);
}

Coding the Singleton

C#
namespace Ordisoftware.Core.ObjectModel
{
  [Serializable]
  abstract public class Singleton<T> where T : Singleton<T>
  {
    static private readonly object locker = new object();

    static protected void DoError(string s)
    {
      throw new SingletonException(SystemManager.Language.Get(s), 
                                   typeof(T));
    }

    static public string Filename
    {
      get { return _Filename; }
      set
      {
        if ( _Filename == value ) return;
        lock ( locker )
        {
          if ( FileTool.Exists(_Filename) ) FileTool.Move(_Filename, value);
          _Filename = value;
        }
      }
    }
    static private volatile string _Filename;

    static public void Save()
    {
      lock ( locker )
        if ( !( _Filename.IsNullOrEmpty() && Instance.IsNull() ) )
        {
          FolderTool.Check(_Filename);
          Instance.Serialize(_Filename);
        }
    }

    ~Singleton()
    {
      try { Save(); }
      catch (Exception e) { ShowError(e.Message); }
    }

    static public T Instance
    {
      get
      {
        lock ( locker )
        {
          if ( _Instance == null )
            if ( FileTool.Exists(_Filename) ) 
                _Instance = (T)_Filename.Deserialize();
            else _Instance = CreateInstance();
          return _Instance;
        }
      }
    }
    static private volatile T _Instance;

    static public T GetInstance()
    {
        return Instance;
    }

    static public T GetPersistentInstance(string filename)
    {
      Filename = filename;
      return Instance;
    }

    static public T GetPersistentInstance()
    {
      if ( _Instance != null ) return _Instance;
      Type type = typeof(T);
      string s = type.Namespace + '.' + type.Name.Replace('`', '_');
      foreach ( Type t in type.GetGenericArguments() )
          s += " " + t.FullName;
      s = SystemManager.FolderSystem + s + SystemManager.ExtObjectFile;
      return GetPersistentInstance(s);
    }

    static private T CreateInstance()
    {
      return (T)CheckImplementation().Invoke(null);
    }

    static internal ConstructorInfo CheckImplementation()
    {
      Type type = typeof(T);
      if ( !type.IsSealed ) DoError("SingletonMustBeSealed");
      var bf1 = BindingFlags.Static | BindingFlags.NonPublic | 
                BindingFlags.Public;
      var bf2 = BindingFlags.Instance | BindingFlags.Public;
      var bf3 = BindingFlags.Instance | BindingFlags.NonPublic;
      if ( type.GetMembers(bf1).Length != 0 )
           DoError("SingletonNoStaticMembers");
      if ( type.GetConstructors(bf2).Length != 0 )
           DoError("SingletonNoPublicConstructors");
      ConstructorInfo[] list = type.GetConstructors(bf3);
      if ( ( list.Length != 1 ) || ( list[0].GetParameters().Length != 0 )
        || ( !list[0].IsPrivate ) )
        DoError("SingletonOnlyOnePrivateConstructor");
      return l[0];
    }
  }
}

Startup Checking 

C#
namespace Ordisoftware.Core
{
  static public class SystemManager
  {
    static public void Initialize()
    {
      Type type = Type.GetType("Ordisoftware.Core.ObjectModel.Singleton`1");
      if ( type != null )
      {
        MethodInfo method;
        string name = "CheckImplementation";
        var bf = BindingFlags.InvokeMethod | BindingFlags.FlattenHierarchy
               | BindingFlags.Static | BindingFlags.NonPublic;
        var list = ObjectUtility.GetClasses(t => ( t.BaseType.Name == type.Name )
                                              && ( t.BaseType.Namespace == type.Namespace ));
        foreach ( var t in list )
          try
          {
            if ( !t.ContainsGenericParameters ) method = t.GetMethod(name, bf);
            else
            {
              Type[] p = t.GetGenericArguments();
              for ( int i = 0; i < p.Length; i++ ) p[i] = typeof(object);
              method = t.MakeGenericType(p).GetMethod(name, bf);
            }
            method.Invoke(null, new object[0]);
          }
          catch ( Exception e ) { b = true; ShowException(e); }
      }
    }
  }
}

Here is the GetClasses function:

C#
static public TypeList GetClasses(Func<Type, bool> select)
{
  return GetList(t => t.IsClass, select);
}

static private TypeList GetList(Func<Type, bool> check, 
               Func<Type, bool> select)
{
  TypeList list = new TypeList();
  Type[] l1 = Assembly.GetExecutingAssembly().GetTypes();
  if ( select == null ) list.AddRange(l1);
  else
    foreach ( Type t in l1 )
      if ( check(t) && select(t) ) list.Add(t);
  Module[] l2 = Assembly.GetEntryAssembly().GetLoadedModules();
  if ( select == null ) list.AddRange(l1);
  else
    foreach ( Module m in l2 )
      foreach ( Type t in m.Assembly.GetTypes() )
        if ( check(t) && select(t) ) list.Add(t);
  list.Sort((v1, v2) => v1.FullName.CompareTo(v2.FullName));
  return list;
}

Example of Usage

Each execution adds 10 to the value displayed by this program:

C#
[Serializable]
public class MySingleton : Singleton<MySingleton>
{
  public int Value { get; set; }
  private MySingleton() { }
}

static class Program
{
  [STAThread]
  static void Main(string[] args)
  {
    SystemManager.Initialize();
    try
    {
      var v = MySingleton.GetPersistentInstance();
      v.Value += 10;
      Console.WriteLine("MySingleton.Value = " + 
                        MySingleton.Instance.Value);
    }
    catch ( Exception e ) { Debugger.ManageException(null, e); }
    finally { SystemManager.Finalize(); }
  }
}

The missing "singleton" Language Keyword

The best way to implement a singleton in C# is to create a static class, but this may cause a problem with serialization and with when the object is initialized, whether one considers laziness.

The ideal thing would be to have a language keyword like singleton: an artifact having no static fields and only one constructor with no parameter and no access modifier. It can be inherited only if marked as abstract. It may be used like a static class but will act like an instantiated class. It may be serializable and disposable: the first usage deserializes the object if a stream is associated or creates a new single instance, disposing serializes the singleton or does nothing if no stream is associated, changing the stream moves the instance from the old to the new, and setting a stream on a singleton already instantiated causes a usage exception if the new stream localizes an item that exists.

C#
[Serializable]
[SingletonPersistence(false)] // don't use a default system stream
public singleton MySingleton
{
  public int Value {get; set; }
  MySingleton()
  {
    // Code executed on first access
  }
}

var stream1 = new SingletonFileStream("c:\\mysingleton.bin");
var stream2 = new SingletonSystemStream();
MySingleton.SetStream(stream1);
MySingleton.Value += 10;
MySingleton.SetStream(stream2);
MySingleton.Value += 10;
MySingleton.SaveState();

Recommended Articles

History

This article was written on July 21st, 2009 based on a vision developed for the Ordisoftware Core Library for .NET project. 

The source code and demo were updated on June 26, 2012.

License

This article, along with any associated source code and files, is licensed under The GNU Lesser General Public License (LGPLv3)


Written By
Technical Lead
France France
C# Desktop Expert

Comments and Discussions

 
GeneralMy vote of 1 Pin
Liqdfire2-Dec-09 7:58
Liqdfire2-Dec-09 7:58 
GeneralRe: My vote of 1 Pin
Clifford Nelson27-Jun-12 9:25
Clifford Nelson27-Jun-12 9:25 
Generali love singletons Pin
radioman.lt2-Dec-09 5:18
radioman.lt2-Dec-09 5:18 
GeneralMy vote of 2 Pin
vtchris-peterson1-Dec-09 9:20
vtchris-peterson1-Dec-09 9:20 
GeneralAfter further thought Pin
PIEBALDconsult28-Jul-09 19:15
mvePIEBALDconsult28-Jul-09 19:15 
GeneralRe: After further thought Pin
Ordisoftware29-Jul-09 2:11
Ordisoftware29-Jul-09 2:11 
GeneralMy vote of 2 Pin
tretyak28-Jul-09 7:31
tretyak28-Jul-09 7:31 
GeneralMy vote of 2 Pin
CoolDadTx28-Jul-09 3:02
CoolDadTx28-Jul-09 3:02 
GeneralRe: My vote of 2 Pin
Ordisoftware28-Jul-09 9:41
Ordisoftware28-Jul-09 9:41 
GeneralOh, no, not another one. PinPopular
PIEBALDconsult27-Jul-09 10:04
mvePIEBALDconsult27-Jul-09 10:04 
GeneralJon Skeet has covered this subject pretty well already Pin
xoox27-Jul-09 7:34
xoox27-Jul-09 7:34 
GeneralRe: Jon Skeet has covered this subject pretty well already Pin
MLansdaal27-Jul-09 9:39
MLansdaal27-Jul-09 9:39 
GeneralWow, people sure like to make stuff more complicated than necessary Pin
Daniel Grunwald27-Jul-09 7:22
Daniel Grunwald27-Jul-09 7:22 
GeneralRe: Wow, people sure like to make stuff more complicated than necessary Pin
PIEBALDconsult27-Jul-09 10:06
mvePIEBALDconsult27-Jul-09 10:06 
GeneralRe: Wow, people sure like to make stuff more complicated than necessary Pin
dvhh30-Jul-09 4:02
dvhh30-Jul-09 4:02 
GeneralRe: Wow, people sure like to make stuff more complicated than necessary Pin
FIorian Schneidereit2-Sep-14 15:56
FIorian Schneidereit2-Sep-14 15:56 
Generallock usage Pin
S. Senthil Kumar27-Jul-09 7:21
S. Senthil Kumar27-Jul-09 7:21 
GeneralRe: lock usage Pin
Ordisoftware27-Jul-09 7:56
Ordisoftware27-Jul-09 7:56 
GeneralRe: lock usage Pin
Axel Rietschin27-Jul-09 10:59
professionalAxel Rietschin27-Jul-09 10:59 
GeneralRe: lock usage Pin
DaZe_AuX28-Jun-12 20:10
DaZe_AuX28-Jun-12 20:10 
C#
if (_Instance == null) {
  lock (locker) {
    if (_Instance == null) {
      _Instance = new Singleton();
    }
  }
}

         
return _Instance;


This would be the correct way.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.