C#Singleton

Pour en savoir plus sur ce schéma de conception, voir ../Developpement/Schemas-conception.html#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;

         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!");

static bool EstRéponseValide(char réponse, char[] candidats) => 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($"{message} ({Concaténer(réponsesValides)}) ");
   char réponse = char.Parse(Console.ReadLine());
   while (!EstRéponseValide(réponse, réponsesValides))
   {
      Console.Write($"Erreur: {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;
}
sealed class GénérateurId
{
   static GénérateurId singleton = null;
   private Current { get; set; } = 0;
   GénérateurId()
   {
   }
   public static GénérateurId GetInstance()
   {
      if (singleton == null)
         singleton = new ();
      return singleton;
   }
   public int Next() => Current++;
}
enum TypeRéponse { OUI, NON }

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 ();
      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;

int val = GénérateurId.Next();
Console.WriteLine($"Nombre généré: {val}");
while (LireRéponse("Un autre?") == TypeRéponse.OUI)
{
   val = GénérateurId.Next();
   Console.WriteLine($"Nombre généré: {val}");
}
Console.WriteLine("Au revoir!");

static bool EstRéponseValide(char réponse, char[] candidats) => 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($"{message} ({Concaténer(réponsesValides)}) ");
   char réponse = char.Parse(Console.ReadLine());
   while (!EstRéponseValide(réponse, réponsesValides))
   {
      Console.Write($"Erreur: {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;
}

/*
   Alternative :
static class GénérateurId
{
   static int Current { get; set; }
   static GénérateurId()
   {
      Current = 0;
   }
   public static int Next() => Current++;
}
*/

static class GénérateurId
{
   static int Current { get; set; } = 0;
   public static int Next() => Current++;
}
enum TypeRéponse { OUI, NON }

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 ();
   private int Current{ get; set; } = 0;
   GénérateurId()
   {
   }
   public static GénérateurId GetInstance()
   {
      if (singleton == null)
      {
         lock (synchro)
         {
            if (singleton == null)
            {
               singleton = new ();
            }
         }
      }
      return singleton;
   }
   public int Next() => Current++;
}
// ...

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 !