Arbeiten mit .NET: Entwurfsmuster/ Creational Patterns/ Singleton
Dieser Buchabschnitt benötigt eine Wikifizierung. Hilfe ist erwünscht! Textbezogenen Fragen und Anmerkungen können auf der Diskussionsseite besprochen werden.
Erzeugt genau eine Instanz eines Objektes in der aktuellen Anwendungsdomäne der CLR (vgl. Muster Singleton).
Implementierung
[Bearbeiten]Die folgende Implementierung enthält die Umsetzung des Design-Musters Singleton.
Diese Implementierung ist nicht threadsicher.
using System; using Org.Wikibooks.De.Csharp.Pattern;
namespace Org.Wikibooks.De.Csharp.Pattern.Creational { class Singleton { // Eine (versteckte) Klassenvariable vom Typ der eigenen Klasse private static Singleton m_Instance;
// Konstuktor // Dieser Konstruktor kann von außen nicht erreicht werden. private Singleton() {}
// Instanziierung public static Singleton getInstance() { // lazy creation if (m_Instance == null) { m_Instance = new Singleton(); } return m_Instance; } } }
Aufruf:
Singleton s = Singleton.getInstance();
Locking
[Bearbeiten]Die wohl simpelste Methode einer threadsicheren Implementierung stellt das Locking dar:
using System; using Org.Wikibooks.De.Csharp.Pattern; namespace Org.Wikibooks.De.Csharp.Pattern.Creational { class Singleton { // Eine (versteckte) Klassenvariable vom Typ der eigenen Klasse private static Singleton instance; private static readonly object m_Lock = new object(); // Konstruktor // Dieser Konstruktor kann von außen nicht erreicht werden. private Singleton() {} // Instanziierung public static Singleton GetInstance() { lock(m_Lock) { if (instance == null) { instance = new Singleton(); } } return instance } } }
In dieser Implementierung wird die Threadsicherheit mit Hilfe eines Kontroll-Objektes (m_Lock) sichergestellt. Jedoch sprechen insbesondere Performancegründe gegen diese Art der Implementierung. Dies versucht die Implementierung des „Double-Check-Lockings“ zu optimieren:
Double-Check-Locking
[Bearbeiten]using System; using Org.Wikibooks.De.Csharp.Pattern; namespace Org.Wikibooks.De.Csharp.Pattern.Creational { class Singleton { // Eine (private) Klassenvariable vom Typ der eigenen Klasse private static Singleton m_Instance; private static readonly object m_Lock = new object(); // Konstuktor // Dieser Konstruktor kann von außen nicht erreicht werden. private Singleton() {} // Instanziierung public static Singleton getInstance() { if (m_Instance == null) { lock(m_Lock) { if (m_Instance == null) { m_Instance = new Singleton(); } } } return m_Instance; } } }
Auch diese Implementierung ist threadsicher. Bevor eine Sperranforderung auf das Kontroll-Objekt (m_Lock) realisiert wird, prüft diese Implementierung, ob eine solche Sperre überhaupt notwendig ist, was die Performance letztlich drastisch verbessert.
Performance
[Bearbeiten]Insbesondere die Implementierung des Double-Check-Lockings wird heiß diskutiert, gilt sie doch weithin als geschickteste Implementierung. Jedoch stellt sich eigentlich die Frage, WO die Performanceeinbrüche tatsächlich verursacht werden.
Üblicherweise werden Singletons eingesetzt, um genau eine Instanz der Klasse in der gesamten Anwendung zu pflegen. Das bedeutet, dass die Initialisierung des Members m_Instance auch nur genau einmal erfolgt, und danach der Vergleich jedesmal true zurückliefert. Schon ab dem zweiten Durchlauf wird also lediglich wahlweise das Multithreading (simples locking) oder die Performance (wiederholte unnötige Vergleiche) drastisch eingeschränkt.
Geht man beispielsweise von einer Anwendung aus, die hunderte Millionen Anforderungen des Singletons in kürzester Zeit benötigt, geschieht dies zumeist in einer Schleife. Hebt man jedoch die Initialisierung des Singletons hier aus der Schleife heraus, initialisiert ihn also vor dem Schleifeneintritt, bricht jede Debatte um die performanteste Implementierung der Instanziierung stante pedes zusammen.
Letzten Endes bleibt es wohl eine Frage der umgebenden Implementierung, wie genau das Singleton Pattern am besten realisiert werden kann.
Einen Ansatz für eine „best practice“-Variante, die wohl in den meisten Situationen optimale Ausgewogenheit bietet, stellt die „lazy instantiation“ dar, die nicht nur ohne Locking, sondern auch ohne Vergleiche auskommt.
Lazy Instantiation
[Bearbeiten]Als lazy instantiation bezeichnet man einen Vorgang, bei dem Objekte erst und nur dann initialisiert werden, wenn sie unmittelbar benötigt werden.
using System; using Org.Wikibooks.De.Csharp.Pattern; namespace Org.Wikibooks.De.Csharp.Pattern.Creational { class Singleton { // public static readonly Singleton instance = new Singleton(); // statischer Konstruktor // Vermeidet in .NET 1.1 die Markierung beforefieldinit static Singleton() {} // Dieser Konstruktor kann von außen nicht erreicht werden. private Singleton() {}
}
Diese wohl einfachste Implementierung des Singleton Patterns nutzt die Effizienz des .NET-Frameworks, um drastisch vereinfachten Code zu schreiben, der zudem threadsicher ist.
Allerdings handelt es sich dabei nicht um eine vollständige Umsetzung des „lazy instantiation“-Ansatzes. Hat diese Klasse beispielsweise weitere statische Methoden, wird die Singleton-Instanz bereits bei deren Aufruf initialisiert.
Full Lazy Instantiation
[Bearbeiten]using System; using Org.Wikibooks.De.Csharp.Pattern; namespace Org.Wikibooks.De.Csharp.Pattern.Creational { class Singleton { // Konstruktor // Dieser Konstruktor kann von außen nicht erreicht werden. private Singleton() {} public static Singleton Instance { get { return Nested.instance; } } class Nested { // statischer Konstruktor static Nested() {} internal static readonly Singleton instance = new Singleton(); } } }
Generische Implementierung
[Bearbeiten]Wikipedia: Implementierung eines Generics für Singletons in C# ab .Net 2.0
Quellen
[Bearbeiten]
Webverweise
[Bearbeiten]Wikibook: Muster Singleton
Implementing the Singleton Pattern in C# (englisch)
Exploring the Singleton Design Pattern (MSDN, englisch)