C#Singleton

On nomme singleton une classe dont on ne trouve pas plus d'une instance dans un programme, et pour laquelle il est même impossible de créer plus d'une instance. Un exemple (qui n'est pas correct pour du code multiprogrammé, mais est acceptable si votre programme n'a qu'un seul thread) serait :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace singleton
{
   sealed class GénérateurId
   {
      static GénérateurId singleton = null;
      int cur;
      GénérateurId()
      {
         cur = 0;
      }
      public static GénérateurId GetInstance()
      {
         if (singleton == null)
            singleton = new GénérateurId();
         return singleton;
      }
      public int Next()
      {
         return cur++;
      }
   }
   class Program
   {
      enum TypeRéponse { OUI, NON };
      static bool EstRéponseValide(char réponse, char[] candidats)
      {
         return candidats.Contains(réponse);
      }
      static string Concaténer<T>(T[] elems)
      {
         string résultat = "";
         if (elems.Length > 0)
         {
            int i = 0;
            résultat += elems[i];
            for (++i; i < elems.Length; ++i)
            {
               résultat += " " + elems[i];
            }
         }
         return résultat;
      }
      static TypeRéponse LireRéponse(string message)
      {
         char [] réponsesValides = { 'o', 'O', 'n', 'N' };
         Console.Write("{0} ({1}) ", message, Concaténer(réponsesValides));
         char réponse = char.Parse(Console.ReadLine());
         while (!EstRéponseValide(réponse, réponsesValides))
         {
            Console.Write("Erreur: {0}. {1} ({2}) ", réponse, message, Concaténer(réponsesValides));
            réponse = char.Parse(Console.ReadLine());
         }
         return réponse == 'o' || réponse == 'O'? TypeRéponse.OUI : TypeRéponse.NON;
      }
      static void Main(string[] args)
      {
         int val = GénérateurId.GetInstance().Next();
         Console.WriteLine("Nombre généré: {0}", val);
         while (LireRéponse("Un autre?") == TypeRéponse.OUI)
         {
            val = GénérateurId.GetInstance().Next();
            Console.WriteLine("Nombre généré: {0}", val);
         }
         Console.WriteLine("Au revoir!");
      }
   }
}

Les éléments clés de ce schéma de conception sont les suivants :

// ...
      static public GénérateurId Instance
      {
         get
         {
            if (singleton == null)
               singleton = new GénérateurId();
            return singleton;
         }
      }
// ...

Une variante possible avec C# serait une classe statique. Pour une adaptation sous cette fornme de l'exemple ci-dessus, nous aurions :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace singleton
{
   static class GénérateurId
   {
      static int cur;
      static GénérateurId()
      {
         cur = 0;
      }
      public static int Next()
      {
         return cur++;
      }
   }
   class Program
   {
      enum TypeRéponse { OUI, NON };
      static bool EstRéponseValide(char réponse, char[] candidats)
      {
         return candidats.Contains(réponse);
      }
      static string Concaténer<T>(T[] elems)
      {
         string résultat = "";
         if (elems.Length > 0)
         {
            int i = 0;
            résultat += elems[i];
            for (++i; i < elems.Length; ++i)
            {
               résultat += " " + elems[i];
            }
         }
         return résultat;
      }
      static TypeRéponse LireRéponse(string message)
      {
         char[] réponsesValides = { 'o', 'O', 'n', 'N' };
         Console.Write("{0} ({1}) ", message, Concaténer(réponsesValides));
         char réponse = char.Parse(Console.ReadLine());
         while (!EstRéponseValide(réponse, réponsesValides))
         {
            Console.Write("Erreur: {0}. {1} ({2}) ", réponse, message, Concaténer(réponsesValides));
            réponse = char.Parse(Console.ReadLine());
         }
         return réponse == 'o' || réponse == 'O' ? TypeRéponse.OUI : TypeRéponse.NON;
      }
      static void Main(string[] args)
      {
         int val = GénérateurId.Next();
         Console.WriteLine("Nombre généré: {0}", val);
         while (LireRéponse("Un autre?") == TypeRéponse.OUI)
         {
            val = GénérateurId.Next();
            Console.WriteLine("Nombre généré: {0}", val);
         }
         Console.WriteLine("Au revoir!");
      }
   }
}

Le singleton tend à être préférable aux classes statiques, cela dit, même si l'écriture avec classe statique semble superficiellement plus simple. En effet :

En situation multiprogrammée, l'approche recommandée pour instancier un singleton avec C# est :

//...
   sealed class GénérateurId
   {
      static GénérateurId singleton = null;
      static object synchro = new object();
      int cur;
      GénérateurId()
      {
         cur = 0;
      }
      public static GénérateurId GetInstance()
      {
         if (singleton == null)
         {
            lock (synchro)
            {
               if (singleton == null)
               {
                  singleton = new GénérateurId();
               }
            }
         }
         return singleton;
      }
      public int Next()
      {
         return cur++;
      }
   }
// ...

La raison pour cette pratique est que, quand un programme fait concurremment plusieurs choses, il est possible que deux threads sollicitent le singleton en même temps. Parfois, dû aux circonstances, le singleton pourrait être créé deux fois :

L'ajout du premier test (le premier if) est une optimisation, évitant de faire le reste du travail si le singleton est déjà créé. Le lock assure qu'un seul thread à la fois puisse passer à l'intérieur du bloc qui suit. Enfin, le second if est pour le cas où deux threads auraient vu le singleton comme étant null et auraient tenté le bloc lock : dans ce cas, un seul créera le singleton, et l'autre constatera qu'il n'est plus null.


Valid XHTML 1.0 Transitional

CSS Valide !