Thread-sicheres Singleton
Teil des Abschnitts Objektorientierte Programmierung der C#-Journey von Coddy — Lektion 52 von 70.
Das Singleton-Muster stellt sicher, dass eine Klasse während der gesamten Anwendung nur eine Instanz hat, und bietet einen globalen Zugriffspunkt darauf. Dies ist nützlich für die Verwaltung gemeinsam genutzter Ressourcen wie Konfigurationseinstellungen, Protokollierungsdienste oder Datenbankverbindungen.
Ein einfaches Singleton verwendet einen privaten Konstruktor, um eine externe Instanziierung zu verhindern, und eine statische Eigenschaft, um den Zugriff zu ermöglichen:
public class Logger
{
private static Logger _instance;
private static readonly object _lock = new object();
private Logger() { } // Privater Konstruktor
public static Logger Instance
{
get
{
lock (_lock)
{
if (_instance == null)
_instance = new Logger();
return _instance;
}
}
}
public void Log(string message) => Console.WriteLine(message);
}Die lock-Anweisung gewährleistet Threadsicherheit – wenn mehrere Threads gleichzeitig versuchen, auf Instance zuzugreifen, kann immer nur einer gleichzeitig den kritischen Abschnitt betreten. Ohne dies könnten zwei Threads _instance beide als null sehen und separate Instanzen erstellen.
C# bietet einen einfacheren thread-sicheren Ansatz unter Verwendung statischer Initialisierung:
public class Logger
{
private static readonly Logger _instance = new Logger();
private Logger() { }
public static Logger Instance => _instance;
public void Log(string message) => Console.WriteLine(message);
}Diese Version ist threadsicher, da die CLR garantiert, dass die Initialisierung statischer Felder nur einmal erfolgt. Beide Ansätze stellen sicher, dass Sie immer dieselbe Instanz erhalten:
Logger.Instance.Log("First call");
Logger.Instance.Log("Same instance");Aufgabe
EinfachLassen Sie uns einen threadsicheren Konfigurationsmanager mit dem Singleton-Muster erstellen. In realen Anwendungen müssen Konfigurationseinstellungen im gesamten Programm zugänglich sein, während gleichzeitig sichergestellt werden muss, dass nur eine Instanz existiert – ein perfekter Anwendungsfall für das Singleton-Muster.
Sie werden Ihren Code in zwei Dateien organisieren:
ConfigManager.cs: Erstellen Sie eineConfigManager-Klasse im NamespaceConfiguration, die das Singleton-Muster implementiert. Ihr Konfigurationsmanager sollte:- Ein privates statisches Feld besitzen, um die einzelne Instanz zu halten
- Einen privaten Konstruktor verwenden, um eine externe Instanziierung zu verhindern
- Eine statische
Instance-Eigenschaft bereitstellen, welche die einzelne Instanz zurückgibt (verwenden Sie den einfacheren Ansatz der statischen Initialisierung für Threadsicherheit) - Ein privates String-Feld
_appNameenthalten, das mit"MyApp"initialisiert ist - Eine Methode
SetAppName(string name)besitzen, um den App-Namen zu aktualisieren - Eine Methode
GetAppName()besitzen, die den aktuellen App-Namen zurückgibt
Program.cs: Demonstrieren Sie in Ihrer Hauptdatei, dass das Singleton korrekt funktioniert, indem Sie mehrmals auf die Instanz zugreifen und zeigen, dass Änderungen über alle Referenzen hinweg bestehen bleiben.
Sie erhalten eine Eingabe:
- Einen neuen Anwendungsnamen, der gesetzt werden soll (z. B.
ProductionServer)
In Ihrem Programm:
- Rufen Sie die
ConfigManager-Instanz ab und geben Sie den Standard-App-Namen aus - Rufen Sie die Instanz erneut ab (in eine andere Variable) und verwenden Sie diese, um den neuen App-Namen aus der Eingabe zu setzen
- Rufen Sie die Instanz ein drittes Mal ab und geben Sie den App-Namen aus, um zu beweisen, dass alle Referenzen dieselben Daten teilen
Wenn die Eingabe beispielsweise ProductionServer ist, sollte die Ausgabe wie folgt aussehen:
MyApp
ProductionServerDies demonstriert den Hauptvorteil des Singletons – egal wie oft Sie auf Instance zugreifen, Sie erhalten immer dasselbe Objekt mit gemeinsamem Zustand. Der Ansatz der statischen Initialisierung garantiert Threadsicherheit, da die CLR sicherstellt, dass das statische Feld nur einmal initialisiert wird, selbst wenn mehrere Threads gleichzeitig darauf zugreifen.
Spickzettel
Das Singleton-Muster stellt sicher, dass eine Klasse während der gesamten Anwendung nur eine Instanz hat, und bietet einen globalen Zugriffspunkt darauf.
Einfaches Singleton mit expliziter Sperrung für Threadsicherheit:
public class Logger
{
private static Logger _instance;
private static readonly object _lock = new object();
private Logger() { } // Privater Konstruktor
public static Logger Instance
{
get
{
lock (_lock)
{
if (_instance == null)
_instance = new Logger();
return _instance;
}
}
}
public void Log(string message) => Console.WriteLine(message);
}Die lock-Anweisung gewährleistet Threadsicherheit, indem sie jeweils nur einem Thread erlaubt, den kritischen Abschnitt zu betreten.
Einfacheres threadsicheres Singleton unter Verwendung statischer Initialisierung:
public class Logger
{
private static readonly Logger _instance = new Logger();
private Logger() { }
public static Logger Instance => _instance;
public void Log(string message) => Console.WriteLine(message);
}Dieser Ansatz ist threadsicher, da die CLR garantiert, dass die Initialisierung statischer Felder nur einmal erfolgt.
Verwendung – gibt immer dieselbe Instanz zurück:
Logger.Instance.Log("First call");
Logger.Instance.Log("Same instance");Probier es selbst
using System;
using Configuration;
class Program
{
public static void Main(string[] args)
{
// Eingabe lesen - der neue Anwendungsname
string newAppName = Console.ReadLine();
// TODO: Die ConfigManager-Instanz abrufen und den Standard-Anwendungsnamen ausgeben
// TODO: Die Instanz erneut abrufen (in eine andere Variable) und sie verwenden, um den neuen Anwendungsnamen zu setzen
// TODO: Die Instanz ein drittes Mal abrufen und den Anwendungsnamen ausgeben, um zu beweisen, dass alle Referenzen dieselben Daten teilen
}
}
Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.
Alle Lektionen in Objektorientierte Programmierung
1Grundlagen der OOP
Externe DateienNamespaces & DirektivenEinführung in Klassen & ObjekteDas 'this'-SchlüsselwortMethoden und ParameterFelder vs. EigenschaftenKonstruktorenObjekt-InitialisiererZusammenfassung - Einfacher Taschenrechner4Vererbung
Grundlagen der Vererbung (:) SyntaxDas 'base'-SchlüsselwortVirtual & Override SchlüsselwörterVersiegelte KlassenDie 'object'-BasisklasseZusammenfassung – Mitarbeiter-Hierarchie7Fortgeschrittene Funktionen
OperatorüberladungIndexer (this[])ToString() überschreibenErweiterungsmethodenZusammenfassung - Benutzerdefinierte Liste10Design Patterns Teil 1
Einführung in Design PatternsThread-sicheres SingletonFactory PatternObserver Pattern (Events)Strategy Pattern2Eigenschaften & Statische Member
Automatisch implementierte EigenschaftenRead-Only- & Write-Only-EigenschaftenStatische Felder & MethodenStatische KlassenExpression-Bodied Member5Polymorphismus & Interfaces
Compile- vs. Runtime-PolymorphismusInterface vs. abstrakte KlasseMehrfache InterfacesExplizite InterfacesUpcasting & DowncastingRecap – Shape Calculator3Klassenarchitektur
Instanz- vs. statische Daten'readonly' & 'const' SchlüsselwörterBacking FieldsZusammenfassung - Bankkonto-Manager6Kapselung
ZugriffsmodifikatorenProperties zur KapselungImplementierung von Data HidingImmutability-MusterRückblick – Studierendendaten