Friday 3 February 2006

Singleton w C#

Chyba najczęściej stosowany wzorzec projektowy. Bardzo często też implementowany błędnie - większość przykładów dostępnych w sieci nie bierze zupełnie pod uwagę wielowątkowości. Przykład na MSDN też nie jest idealny. Dobra, najpierw działający kod:


/// <summary></summary>
public sealed class Singleton
{
#region Singleton
/// <summary>ukryty konstruktor</summary>
private Singleton()
{}

/// <value>Dostep do instancji singletona</value>
public static Singleton Instance
{ get { return InstaceHolder.instance; } }

/// <summary>klasa wewnętrzna przechowująca instancję</summary>
class InstaceHolder
{
/// <summary>konstruktor statyczny,
/// by kompilator NIE oznaczyl klasy jako BeforeFieldInit</summary>
static InstaceHolder() {}

/// <summary>instancja singletona</summary>
internal static readonly Singleton instance = new Singleton();
}
#endregion
}

I po kolei, o co w tym chodzi. Sama klasa oznaczona jako sealed, czyli nie można z niej dziedziczyć. Nie chcemy, by ktos stworzył klasę potomną, która będzie pozwalała na utworzenie wielu instancji. Funkcjonalność zbliżoną do polimorfizmu może nam łatwo dostarczyć np. wzorzec State.


Konstruktor prywatny, żeby nikt nam "na boku" nie utworzył instancji klasy.


Własność publiczna Instance - jedyny dostęp do singletona. Metody niestatyczne będzie się w nim wywoływać przez Singleton.Instance.MojaMetoda();


Klasa wewnętrza pozwala nam na leniwe, czyli opóźnione utworzenie instancji. Dopiero pierwsze odwołanie do Singleton.Instance utworzy obiekt. Kostruktor statyczny w InstanceHolder wymusza na kompilatorze, by nie oznaczał klasy jako BeforeFieldInit. To znaczy, że pola statyczne zostaną zainicjowane dopiero przy pierwszym odwołaniu do klasy (dostęp do statycznego pola/metody lub utworzenie instancji), a nie "na zapas". Uwaga - chodzi o pola/metody klasy InstanceHolder, dostęp do statycznych pól czy metod Singletona nie spowoduje zainicjalizowania go.


Inicjalizacja singletona powinna trafić do prywatnego konstruktora, a nie statycznego. W ogóle - ze statycznymi konstruktorami lepiej ostrożnie. InstanceHolder.instance mógłbym zainicjalizować w static InstaceHolder(), ne? Haczyk znajduje się w specyfikacji języka C#.

The static constructor for a class executes at most once in a given application domain.

Najwyżej raz oznacza, że może nie zostać wykonany ani razu. Tak się dzieje np. w niektórych wersjach Mono.



Kod pochodzi z yoda.arachsys.com



UPDATE: na MSDN znalazłem poprawny i elegancki kod singletona, z innym podejściem niż pokazane powyżej.