Il pattern Singleton in C#

Giorgio Borelli

Il design pattern Singleton in C SharpNell'ambito della programmazione OOP i design patterns non sono altro che delle soluzioni efficaci, testate e funzionanti a problemi comuni. Utilizzare un pattern al momento opportuno si rivela sicuramente una soluzione elegante ed efficace, ed evita di ri-scrivere del codice potenzialmente non ottimizzato o contentente dei bug. Tuttavia i patterns non rappresentano delle regole scritte per una buona programmazione, più che altro forniscono delle linee guida da seguire quando uno sviluppatore deve mettere appunto un determinata soluzione.

I design patterns sono diversi, ed è celebra la loro raccolta nel libro “Design Patterns: Elements of Reusable Object-Oriented Software” ad opera del Gof (Gang of four - la banda dei quattro), quattro esperti programmatori di fama, i quali hanno suddiviso i patterns in tre famiglie: strutturali, creazionali e comportamentali.

Oggi vogliamo prendere in considerazione il pattern Singleton, appartenente ai patterns creazionali, specifici per la creazione d'istanze. Lo scopo del pattern Singleton è quello di permettere per una classe la creazione di una ed una sola istanza con un'unico punto d'accesso (entry-point) all'istanza a livello globale.

Andiamo a scoprire come s'implementa un pattern Singleton in C# e quali caratteristiche e vantaggi comporta.

Il pattern Singleton è sicuramente uno dei pattern più conosciuti e diffusi, sia per la sua semplicità che per la sua efficacia, questo trova differenti implementazioni tutte molto valide che perlopiù dipendono dal contesto in cui vanno applicate, vediamo in particolar modo un'implementazione non thread-safety per ambienti singlethread e quella thread-safety.

 

Pattern Singleton non thread-safety

Cominciamo con illustrare il pattern Singleton nella sua forma più semplice, ovvero nella inizializzazione statica o anche conosciuta come lazy initialization (inizializzazione pigra) , poichè l'unica istanza della classe non viene creata nel costruttore e viene creata solamente quando serve, il codice è:

//classe Singleton di tipo sealed non ereditabile
publicsealedclassSingleton {
    //membro privato che rappresenta l'instanza della classe
    privatestaticSingleton _instance;

    //costruttore privato senza param non accessibile dall'esterno della classe
    private Singleton() {}

    //Entry-Point: proprietà esterna che ritorna l'istanza della classe
    publicstaticSingleton Instance
    {
         get
         {
              if (_instance == null)
              {
                   _instance = newSingleton();
              }
              return _instance;
          }
     }
}

 

classProgram
{
    staticvoid Main(string[] args)
    {
         Singleton s1 = Singleton.Instance;
         Singleton s2 = Singleton.Instance;
         if (s1 == s2)
             Console.WriteLine("Instanza Singleton Unica");
         else
             Console.WriteLine("Instanza Singleton Differente");

         Console.ReadLine();
     }
}

I punti del codice su cui dobbiamo porre maggiore attenzione sono:

  • la classe Singleton è dichiarata come sealed (non ereditabile) per evitare che classi ereditate possano creare ulteriori istanze;
  • il membro statico e privato _instance dello stesso tipo della classe Singleton sarà l'oggetto restituito come unica istanza della classe, inoltre essendo un membro statico può accedervi solo un metodo statico (GetInstance);
  • il costruttore della classe (senza parametri) è dichiarato come privato in modo che non sia accessibile dall'esterno ma solo dall'interno della classe stessa, come avviene nel metodo GetInstance;
  • il metodo statico GetInstance rappresenta l'entry-point di accesso alla classe e ne restituisce l'istanza che è sempre unica in quanto al suo interno vi è l'apposito controllo che richiama il costruttore alla sola prima invocazione del metodo.

Questa implementazione del pattern Singleton in C# presenta però un bug, o meglio va bene solo per le applicazioni single-thread, per tutti i programmi multi-thread ogni thread che invoca la proprietà Instance crea una nuova istanza della classe.

 

Pattern Singleton thread-safety

Per essere certi che il pattern Singleton sia thread-safe il codice và modificato in questo modo:

//classe Singleton di tipo sealed non ereditabile
public sealed class Singleton {
    //membro privato che rappresenta l'instanza della classe
    private static Singleton _instance;

    //membro privato per la sincronizzaz dei thread
    private static readonly Object _sync = new Object();

    //costruttore privato non accessibile dall'esterno della classe
    private Singleton() {}

    //Entry-Point: proprietà esterna che ritorna l'istanza della classe
    public static Singleton Instance
    {
         get
         {
              //per evitare richieste di lock successive alla prima istanza
              if (_instance == null)
              {
                   lock (_sync) //area critica per la sincronizz dei thread
                   {
                        //vale sempre per la prima istanza
                        if (_instance == null)
                        {
                             _instance = new Singleton();
                        }
                   }
              }
              return _instance;
        }
    }
}

In questa seconda implementazione il pattern Singleton è thread safety infatti l'area critica definita dalla parola chiave lock* assicura un'esclusione reciproca per ogni thread, inoltre nell'entry-point viene messo un doppio controllo sull'istanza, il secondo if, quello più esterno è stato aggiunto per evitare che il lock sia richiamato ogni volta che un thread invoca la proprietà, comportando un'assurdo spreco di risorse, con questo ulteriore if invece il lock viene attivato solo alla creazione della prima istanza.

*: L'istruzione lock impedisce a un thread di accedere a una sezione critica del codice, mentre un altro thread è già presente in tale sezione. Se un altro thread tenta di accedere a un codice bloccato, attenderà (in stato di blocco) finché l'oggetto non verrà rilasciato (MSDN).

 

Conclusioni

Le due implementazioni del Singleton viste non sono le sole, in particolare per quelle multi-thread sono possibili ulteriori migliorie, chi volesse approfondire per esigenze proprie può cercare nella documentazione in linea di MSDN, quello che mi preme sottolineare è l'importanza del Singleton, questo pattern creazionale ci assicura l'istanza unica di una classe con un solo punto di accesso in tutta l'applicazione, questo comporta notevoli vantaggi, tra i quali:

  • Accesso controllato all'unica istanza della classe
  • Evitare di creare svariati oggetti condivisi e variabili globali
  • Avere una soluzione di sviluppo testata e funzionante

Questi vantaggi non sono cosa da poco, poichè ne conseguono una pulizia ed una manutenibilità del codice molto elevata, un codice ottimizzato con uno spreco di risorse ed una gestione degli oggetti in memoria tale da migliorare notevolmente le performance. Desidero lasciarvi con un'idea pratica sull'utilizzo del pattern Singleton in C#, ad es. questo si adatta benissimo ad essere usato in un'applicazione web n-tier, col fine di creare una classe singleton per la gestione della connessione al database.

Ovviamente, invito tutti i lettori ad esprimere tramite i commenti le proprie opinioni sul pattern Singleton e sopratutto ad indicare degli esempi pratici d'utilizzo.

Categorie: C# | Programmazione

Tags: ,

Aggiungi Commento

biuquote
Loading