C# Language
Implementacja singletonu
Szukaj…
Statycznie zainicjowany singleton
public class Singleton
{
private readonly static Singleton instance = new Singleton();
private Singleton() { }
public static Singleton Instance => instance;
}
Ta implementacja jest bezpieczna dla wątków, ponieważ w tym przypadku obiekt instance
jest inicjowany w konstruktorze statycznym. CLR już zapewnia, że wszystkie statyczne konstruktory są zabezpieczone przed wątkami.
Mutowanie instance
nie jest operacją bezpieczną dla wątków, dlatego atrybut readonly
do readonly
gwarantuje niezmienność po inicjalizacji.
Leniwy, bezpieczny wątek Singleton (przy użyciu podwójnie sprawdzonego ryglowania)
Ta bezpieczna dla wątków wersja singletonu była niezbędna we wczesnych wersjach platformy .NET, gdzie nie można zagwarantować, że static
inicjalizacja jest bezpieczna dla wątków. W bardziej nowoczesnych wersjach frameworka zwykle preferowany jest statycznie zainicjowany singleton, ponieważ bardzo łatwo jest popełnić błędy implementacyjne w następujący sposób.
public sealed class ThreadSafeSingleton
{
private static volatile ThreadSafeSingleton instance;
private static object lockObject = new Object();
private ThreadSafeSingleton()
{
}
public static ThreadSafeSingleton Instance
{
get
{
if (instance == null)
{
lock (lockObject)
{
if (instance == null)
{
instance = new ThreadSafeSingleton();
}
}
}
return instance;
}
}
}
Zauważ, że sprawdzenie if (instance == null)
odbywa się dwa razy: raz przed nabyciem blokady, i raz później. Ta implementacja nadal byłaby bezpieczna dla wątków, nawet bez pierwszego sprawdzenia zerowego. Oznaczałoby to jednak, że blokada byłaby uzyskiwana za każdym razem, gdy wystąpiono o instancję, a to powodowało pogorszenie wydajności. Pierwsze sprawdzenie zerowe jest dodawane, aby blokada nie została uzyskana, chyba że jest to konieczne. Drugie sprawdzenie zerowe upewnia się, że tylko pierwszy wątek, który uzyska blokadę, tworzy instancję. Inne wątki znajdą instancję, która ma zostać wypełniona, i przejdą do przodu.
Leniwy, bezpieczny wątek Singleton (używając Lazy )
Lazy typu .Net 4.0 gwarantuje bezpieczną inicjalizację wątków, więc ten typ może być użyty do tworzenia singletonów.
public class LazySingleton
{
private static readonly Lazy<LazySingleton> _instance =
new Lazy<LazySingleton>(() => new LazySingleton());
public static LazySingleton Instance
{
get { return _instance.Value; }
}
private LazySingleton() { }
}
Użycie Lazy<T>
upewni się, że obiekt jest tworzony tylko wtedy, gdy jest używany gdzieś w kodzie wywołującym.
Proste użycie będzie takie jak:
using System;
public class Program
{
public static void Main()
{
var instance = LazySingleton.Instance;
}
}
Wersja demonstracyjna na żywo .NET Fiddle
Leniwy, wątkowy bezpieczny singleton (dla .NET 3.5 lub starszych, alternatywna implementacja)
Ponieważ w .NET 3.5 i starszych nie masz klasy Lazy<T>
, używasz następującego wzorca:
public class Singleton
{
private Singleton() // prevents public instantiation
{
}
public static Singleton Instance
{
get
{
return Nested.instance;
}
}
private class Nested
{
// Explicit static constructor to tell C# compiler
// not to mark type as beforefieldinit
static Nested()
{
}
internal static readonly Singleton instance = new Singleton();
}
}
Jest to inspirowane postem na blogu Jona Skeeta .
Ponieważ Nested
klasa jest zagnieżdżona i prywatnym instancji instancji singleton nie zostanie wyzwolony przez dostęp do innych członków Sigleton
klasy (takich jak właściwości tylko do odczytu publicznego, na przykład).
Usuwanie instancji Singleton, gdy nie jest już potrzebna
Większość przykładów pokazuje tworzenie instancji i trzymanie obiektu LazySingleton
aż do zakończenia aplikacji LazySingleton
właścicielem, nawet jeśli obiekt ten nie jest już potrzebny aplikacji. Rozwiązaniem tego jest zaimplementowanie IDisposable
i ustawienie instancji obiektu na null w następujący sposób:
public class LazySingleton : IDisposable
{
private static volatile Lazy<LazySingleton> _instance;
private static volatile int _instanceCount = 0;
private bool _alreadyDisposed = false;
public static LazySingleton Instance
{
get
{
if (_instance == null)
_instance = new Lazy<LazySingleton>(() => new LazySingleton());
_instanceCount++;
return _instance.Value;
}
}
private LazySingleton() { }
// Public implementation of Dispose pattern callable by consumers.
public void Dispose()
{
if (--_instanceCount == 0) // No more references to this object.
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
// Protected implementation of Dispose pattern.
protected virtual void Dispose(bool disposing)
{
if (_alreadyDisposed) return;
if (disposing)
{
_instance = null; // Allow GC to dispose of this instance.
// Free any other managed objects here.
}
// Free any unmanaged objects here.
_alreadyDisposed = true;
}
Powyższy kod usuwa instancję przed zakończeniem aplikacji, ale tylko wtedy, gdy konsumenci wywołują obiekt Dispose()
po każdym użyciu. Ponieważ nie ma gwarancji, że tak się stanie, ani sposobu na wymuszenie tego, nie ma też gwarancji, że instancja zostanie kiedykolwiek usunięta. Ale jeśli ta klasa jest używana wewnętrznie, łatwiej jest upewnić się, że metoda Dispose()
jest wywoływana po każdym użyciu. Oto przykład:
public class Program
{
public static void Main()
{
using (var instance = LazySingleton.Instance)
{
// Do work with instance
}
}
}
Pamiętaj, że ten przykład nie jest bezpieczny dla wątków .