Kategorien

Navigation

Feeds/Meta

Blog RSS Feed
Kommentare RSS Feed

Manuel Josupeit-Walter - XING Profil
Manuel Josupeit-Walter - Amazon Wunschliste
Manuel Josupeit-Walter - XMPP (Jabber)
Manuel Josupeit-Walter - ICQ
Manuel Josupeit-Walter - Skype

Bloggeramt.de
BlogAlm
Add to Technorati Favorites

SpamPoison

josupeit.com > Weblog > Informatik und Technik > C# und .NET im Allgemeinen > Implementierung des Singleton

Implementierung eines Singleton in C#

Datum:   21.11.2007, 09:59 Uhr
Kategorie:   C# und .NET im Allgemeinen Feed dieser Kategorie abonnieren
Kommentare:   2, Neuen Kommentar schreiben

Es gibt Situationen, in denen man als Entwickler sicherstellen möchte, dass von einer Klasse nur ein Objekt instanziiert werden kann. Für diesen Anwendungsfalls gibt es bereits einen Lösungsansatz: Das Singleton-Pattern1. Das Prinzip dieses Ansatzes ist recht simpel und beruht unter anderem auf der Nutzung statischer Felder und Methoden. Diese sogenannten statischen Member einer Klasse unterscheiden sich von den Instanzmembern in sofern, als dass sie genutzt werden können, ohne explizit ein Objekt zu instanziieren.

Am Beispiel C# (Mircosoft .NET Framework 2.0) möchte ich an dieser Stelle in zwei Schritten eine Art systemweiten Singleton implementieren, dieser Artikel befasst sich allerdings zunächst mit dem ersten Schritt: der Implementierung des herkömmlichen Singleton. Die Erweiterung einer bestehenden Klasse zum Singleton funktioniert in C# wie folgt:


public class Beispiel
{
  /*
   * Dieses statische Feld hält unsere Instanz der
   * Klasse
   */
  private static Beispiel _Instanz;
 
  /*
   * Konstruktor der Klasse wird über den Zugriffsmodifizierer
   * "private" geschützt, um eine Instanziierung der Klasse von
   * außen zu unterbinden.
   */
  private Beispiel()
  {
    // TODO: Implementieren Sie Ihren Konstruktor hier
  }
 
  /*
   * Statische Methode, die die Instanz der Klasse zurück gibt.
   */
  public static Beispiel GetInstance()
  {
   if (_Instanz == null)
    _Instanz = new Beispiel();

   return _Instanz;
  }
}

Anhand dieses Beispiels sehen Sie, dass nur eine Instanz der Klasse Beispiel erzeugt wird und zwar über die statische Methode GetInstance():


public static void main(string[] args)
{
 Beispiel BeispielInstanz = Beispiel.GetInstance();
}

Der direkte Aufruf von BeispielInstanz = new Beispiel(); ist unzulässig, da der Konstruktor der Klasse privat ist.

Um auf einfachem Wege ihren Quelltext wiederverwenden zu können, lagern wir nun diesen Code in eine Bibliothek aus. Dieses Vorhaben allerdings erscheint nur auf den ersten Blick einfach: über Vererbung. Allerdings kann aus der Basisklasse nicht ohne Weiteres auf die abgeleitete Klasse zugegriffen werden, dies widerspräche auch dem Sinn von Vererbung, so dass folgendes Vorhaben nicht funktioniert:


public abstract class Singleton
{
  private static Singleton _Instanz;
 
  private Singleton()
  {
    // TODO: Implementieren Sie Ihren Konstruktor hier
  }
 
  public static Singleton GetInstance()
  {
   if (_Instanz == null)
     // Hier müsste die Kindklasse instanziiert werden
    _Instanz = new ?();

   return _Instanz;
  }
}

Abgesehen von diesem Problem kann eine abgeleitete Klasse so nicht instanziiert werden, da die Basisklasse durch die Verwendung des Schlüsselwortes private den Konstruktor schützt, ein Zugriff auf diesen Konstruktor durch Reflektion über die Basisklasse ist allerdings auch nicht möglich2.

Das Zauberwort an dieser Stelle lautet Generika3, denn generische Klassen erlauben es dem Entwickler, Typdefinitionen für ganze Klassen oder Methoden offen zu lassen. Wir entwickeln unser Singleton-Pattern also weiter zu einer statischen Klasse, also einer Klasse, die selbst nicht instanziiert werden kann, die aber unsere Singleton-Objekte verwaltet. Diese Klasse soll den Namen SingletonProvider bekommen. Damit künftig einfach entschieden werden kann, ob es sich um eine neue Instanz handelt oder nicht, erweitern wir gleich unsere Methode GetInstance() um einen boolschen Wert:


using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
using System.Reflection;

public static class SingletonProvider
{
  /*
   * Die Hashtabelle Selfs hält unsere Instanz-
   * objekte intern im Speicher.
   */

  private static Hashtable Selfs = new Hashtable();
 
  // Dieses Objekt wird zur Threadsynchronisation verwendet
  private static object Lock     = new Object();

  public static T GetInstance<T>(out bool New)
    where T: new()
  {
    // Threadsynchronisation
    lock (Lock) {
      /*
       * Überprüfen, ob bereits eine Instanz
       * des generischen Typen T existiert.
       * Als Schlüssel für die Hashtabelle verwenden
       * wir die GUID des Typen.
       */

      if (Selfs.ContainsKey(typeof(T).GUID))
      {
        // Instanz existiert bereits
        New = false;
        return (T)Selfs[typeof(T).GUID];
      }

      // Neue Instanz erstellen
      Selfs.Add(typeof(T).GUID, new T());
      New = true;
     
      return (T)Selfs[typeof(T).GUID];
    }
  }
}

Durch diese Klasse können nun auf einfachem Wege Singleton-Objekte erzeugt werden:


public static void main(string[] args)
{
 bool neueInstanz;
 Beispiel MeinBeispiel = SingletonProvider.GetInstance<Beispiel>(out neueInstanz);
}

Die Variable MeinBeispiel enthält nun eine neue Instanz der Klasse Beispiel, die Variable neueInstanz ist wahr, falls der SingletonProvider ein neues Objekt erzeugt hat. Der Typ Beispiel ist genau der "Lückenfüller" für unseren generischen Typen T im Beispiel oben. Einzige Bedingung: Es muss sich um eine Klasse handeln, die einen parameterlosen, öffentlichen Konstruktor besitzt (siehe where T: new()). Dies allerdings widerspricht wiederum dem Singleton-Gedanken, denn nun kann wieder eine Instanz der Klasse von Außen erzeugt werden: MeinBeispiel = new Beispiel(); Der Singleton ist somit ausgehebelt.

Wir modifizieren an dieser Stelle unseren SingletonProvider, so dass ausschließlich Klassen mit geschützten Konstruktoren verwendet werden können. Außerdem überladen wir unsere GetInstance-Methode der Einfachheit halber wie folgt:


using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
using System.Reflection;

public static class SingletonProvider
{
  private static Hashtable Selfs = new Hashtable();
  private static object Lock     = new Object();

  public static T GetInstance<T>(out bool New)
    where T: class // Jede Klasse ist erlaubt
  {
    // Threadsynchronisation
    lock (Lock) {
      if (Selfs.ContainsKey(typeof(T).GUID))
      {
        // Instanz existiert bereits
        New = false;
        return (T)Selfs[typeof(T).GUID];
      }

      // Neue Instanz über Reflektion erstellen
      ConstructorInfo ctorInfo;
     
      // Geschützte Konstruktoren auslesen
      ctorInfo = typeof(T).GetConstructor(
                  BindingFlags.NonPublic |
                  BindingFlags.Instance,
                  null,
                  Type.EmptyTypes,
                  null
                );
               
      // Konstruktor ohne Parameter aufrufen
      T _Instanz = (T)ctorInfo.Invoke(new object[] { });
     
      // Instanz der Hashtabelle zuführen
      Selfs.Add(typeof(T).GUID, _Instanz);
      New = true;
     
      return (T)Selfs[typeof(T).GUID];
    }
  }
 
  /*
   * Für den Fall, dass es egal ist,
   * ob es sich um ein neues Objekt handelt
   */

  public static T GetInstance<T>()
    where T: class
  {
    bool _Trash;
    return GetInstance<T>(out _Trash);
  }
}

Nun wird der private parameterlose Konstruktor zur Instanziierung verwendet. Als nächstes verbieten wir ausdrücklich öffentliche Konstruktoren und schmeißen andernfalls einen Fehler:


using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
using System.Reflection;

public static class SingletonProvider
{
  private static Hashtable Selfs = new Hashtable();
  private static object Lock     = new Object();

  public static T GetInstance<T>(out bool New)
    where T: class // Jede Klasse ist erlaubt
  {
    // Auf öffentlichen Konstruktor prüfen
    ConstructorInfo checkCtor = (typeof(T)).GetConstructor(Type.EmptyTypes);
   
    /*
     * Falls es einen solchen Konstruktor gibt,
     * schmeißen wir einen Fehler, da es dem
     * Charakter eines Singleton widerspricht.
     */

    if (checkCtor != null)
      throw new InvalidOperationException("Singleton means that you don't have any public constructors");

    // Threadsynchronisation
    lock (Lock) {
      if (Selfs.ContainsKey(typeof(T).GUID))
      {
        // Instanz existiert bereits
        New = false;
        return (T)Selfs[typeof(T).GUID];
      }

      // Neue Instanz über Reflektion erstellen
      ConstructorInfo ctorInfo;
     
      // Geschützte Konstruktoren auslesen
      ctorInfo = typeof(T).GetConstructor(
                  BindingFlags.NonPublic |
                  BindingFlags.Instance,
                  null,
                  Type.EmptyTypes,
                  null
                );
               
      // Konstruktor ohne Parameter aufrufen
      T _Instanz = (T)ctorInfo.Invoke(new object[] { });
     
      // Instanz der Hashtabelle zuführen
      Selfs.Add(typeof(T).GUID, _Instanz);
      New = true;
     
      return (T)Selfs[typeof(T).GUID];
    }
  }
 
  public static T GetInstance<T>()
    where T: class
  {
    bool _Trash;
    return GetInstance<T>(out _Trash);
  }
}

Mit Hilfe der obigen Implementierung der SingletonProvider-Klasse lassen sich nun beliebige Singleton-Objekte verwalten:


public class Beispiel
{

 private Beispiel()
 {
  Console.WriteLine("Klasse instanziiert");
 }
 
 public static void main(string[] args)
 {
  Beispiel MeinBeispiel  = SingletonProvider<Beispiel>.GetInstance();
  Beispiel MeinBeispiel2 = SingletonProvider<Beispiel>.GetInstance();
 }
}

Dieses Beispiel erzeugt auf der Konsole einmalig die Ausgabe "Klasse instanziiert", da der erste Aufruf von GetInstance() eine Instanz erzeugt. Der zweite Aufruf hingegen liefert ganz im Sinne des Singleton das selbe Objekt. Eine Instanz von Außen über MeinBeispiel = new Beispiel(); ist durch den geschützten Konstruktor übrigens nicht mehr möglich.

Möchte man nun eine Klasse schreiben, um andere Klassen von dieser "Singleton-Basis" erben zu lassen, bedienen wir uns der oben bereits implementierten Klasse SingletonProvider, der Singleton selbst soll hier allerdings nicht instanziiert werden und ist deshalb abstrakt:


public abstract class SingletonBase<T>
  where T: SingletonBase<T>
{
  public static T GetInstance(out bool New)
  {
    return SingletonProvider.GetInstance<T>(out New);
  }
 
  public static T GetInstance()
  {
    bool _Trash;
    return GetInstance(out _Trash);
  }
}

Da, wie oben bereits gesagt, die Elternklasse allerdings nicht auf die Kindklasse schließen kann, werwenden wir für die Anweisung where T: SingletonBase<T>, um den Typen zu "ermitteln". Dies ist zwar in gewisser Hinsicht eine Redundanz, da eigentlich bereits durch die Vererbung klar ist, dass eine Instanz von "Beispiel" erzeugt werden soll, leider gibt es meiner Ansicht nach aber derzeit keine wesentlich elegantere Lösung dieses Problems. Das .NET Framework 3.5 lässt allerdings bereits heute auf schönere Ansätze hoffen...


public sealed class Beispiel : SingletonBase<Beispiel>
{
    private Beispiel()
    {
     // Konstruktor
    }
}

Instanzen der Klasse können nun einfach via Beispiel.GetInstance() geholt werden, eine eigene Instanziierung durch Verwendung des Schlüsselwortes new() ist nicht mehr möglich. Ein Haken allerdings bleibt: Die Klasse sollte versiegelt sein, so dass von ihr nicht weiter geerbt werden kann, denn


class GeerbtesBeispiel : Beispiel
{ }

bietet nun auch die statische Methode GetInstance(), die allerdings nach wie vor die Basisklasse Beispiel instanziiert, nicht jedoch GeerbtesBeispiel. 

Damit nun dieser Artikel nicht noch länger wird, als er bisher schon ist, möchte ich dieses Beispiel beim nächsten Mal zu einem systemweiten Singleton ausbauen. Mit Hilfe dieser Methode lässt sich beispielsweise einfach realisieren, dass eine Anwendung nur einmal gestartet werden kann und jeder weitere Programmstart das bereits geöffnete Fenster in den Vordergrund holt. Dazu allerdings erst beim nächsten Mal mehr.

Abschließend bleibt mir eigentlich nur noch zu sagen, dass es sich bei diesem Artikel lediglich um ein Beispiel einer Möglichkeit der Umsetzung handelt. Es gibt noch unzählige weitere Implementierungen des Singleton, sehen Sie es also als eine Art Inspiration für Ihre Projekte, ich hoffe ich konnte Ihnen jedoch ein wenig bei der Lösung Ihres Problemes weiterhelfen.


GPLv3
1 Singleton (Entwurfsmuster): Wikipedia
2 Type.GetConstructor-Methode: Microsoft
3 Generische Typen: Wikipedia

 


Siehe auch:
Sprachelemente von C-Sharp (Wikipedia)




Bisherige Kommentare: RSS Feed der Kommentare

Name:  Erik Nagel
Datum:  03.09.2010, 07:14 Uhr
URL:  http://
Kommentar:  !!!
:-))

Name:  Michael
Datum:  28.03.2009, 10:39 Uhr
URL:  http://
Kommentar:  Sehr feines, sehr nützliches Tool!! Da hab' ich doch gleich alle eigenen Singletons in die Tonne gehauen ;-)

Bisherige Trackbacks:
Keine