Prosty Singleton

Rekomendowana implementacja wzorca Singleton w C# wygląda tak (plus oczywiście prywatny konstruktor):

  1:  private static object _lock = new object();
  2:  private static Singleton _instance;
  3:  public static Singleton Instance
  4:  {
  5:  	get
  6:  	{
  7:  		if (_instance == null)
  8:  		{
  9:  			lock (_lock)
 10:  			{
 11:  				if (_instance == null)
 12:  				{
 13:  					_instance = new Singleton();
 14:  				}
 15:  			}
 16:  		}
 17:  		return _instance;
 18:  	}
 19:  }

Czy jednak nie można zawrzeć całego tego "szumu" w jednej linijce kodu?

  1:  public static readonly Singleton Instance = new Singleton();

Cechy współdzielone z implementacją pierwszą:
- spełnione założenia Singletona: jedna, dostępna globalnie instancja
- bezpieczna wielowątkowość
Zalety:
- jedna banalnie prosta linijka kodu
Wady:
- możliwa inicjalizacja przed pierwszym faktycznym wykorzystaniem instancji

Z tej implementacji korzystam od dawna i... czy coś mi umknęło? Teoretyczne odłożenie procesu tworzenia obiektu kosztuje nas 18 linii kodu? W ogromnie przeważającej większości przypadków czytelność i mniejsza ilość kodu są z pewnością ważniejsze. A może jednak nie zauważam jakiejś oczywistości?

Opublikowane 22 kwietnia 09 03:43 przez Procent
Filed under: , ,

Komentarze:

# Gutek said on kwietnia 22, 2009 16:18:

@Procent

mhhh, ze strony MS:

http://msdn.microsoft.com/en-us/library/ms998558.aspx

proponuja ta samo co ty tylko za pomoca property (przyklad srodkowy).

Co do Twojego rozwiazania, jest zgrabne i ladne :) ale jak to sie zachowa w srodowisku wielowatkowym?

Tutaj jest o tym cala dyskusjka - problemie z lockiem itp:

http://blogs.msdn.com/brada/archive/2004/05/12/130935.aspx - plus linki do art na ten temat w .NET 2.0 i 3.5

zas tutaj trafiam dosc czesto nie zaleznie czego szukam ;)

http://www.yoda.arachsys.com/csharp/singleton.html

Gutek

PS: Ja osobiscie sobie swojego czasu stworzylem szablony do klas w VS i teraz jak chce Singleton to klikam create i mam :) Wiec... myslac o zaoszczedzaniu czasu na pisanie kodu, to sadze ze juz te jedna linijka mi sie zwrocila :)

PS2: a teraz pora na ciemna strone mocy: Why Singletons are Evil! :) http://blogs.msdn.com/scottdensmore/archive/2004/05/25/140827.aspx

# jakubin said on kwietnia 22, 2009 20:22:

Problemy są jak pojawi się wyjątek w konstruktorze obiektu Singletonu. Wtedy cała klasa jest kompletnie bezużyteczna. Przykład:

class Program

{

   static void Main(string[] args)

   {

       var name = MySingleton.Name;

   }

}

public class MySingleton

{

   public static readonly string Name = "aaa";

   public static readonly MySingleton Instance = new MySingleton();

   private MySingleton()

   {

       throw new Exception();

   }

}

Powyższy kod wywala TypeInitializationException. Ponadto, wersja 18 linijkowa daje Ci ponowną szansę na utworzenie obiektu w razie gdy za pierwszym raz wyskoczy exception.

# Wojciech Gebczyk said on kwietnia 22, 2009 21:56:

Gutek: mozesz podac dokladniej o co chodzi z tym problemem wielowatkowosci w przypadku "jednolinijkowca"?

Gutek: to "evil costam" to tak troche nie na temat. w KAZDYM wzorcu/szablonie programowania mozna przegiac (http://discuss.joelonsoftware.com/default.asp?joel.3.219431.12), wiec taki disclaimer mozna dowac gdzie sie chce i to nic nie wnosi - nawet jestem w stanie chyba podac takiego "evila" dla dowolnego najwspanialeszego przykladu wzorca/solution jakie zaproponujesz. Poprostu nie mozna programowac na "małpke", tylko trzeba myslec.

jakubin: hmm.. przyklad nietrafiony z ta "ponowna szansa na utowrzenie obiektu". Zakladamy ze mamy  ten sam typ/klase (rozna implementacja "wnetrznosci" powoduje nieporownywalnosc rozwiazan, bo... sa inne) ale o roznej implementacji "singletona". W takim wypadku (jesli ctor wyrzuca wyjatek) to nie wazne co zrobic jesli zawsze bedzie wyjatek, tyle ze co najwyzej innego typu (a jak wiadomo typ wyjatku jest dla programisty a nie uzytkownika).

generalnie wyglada jakby kazdy z was mial na mysli pewien zestaw zastosowania singletona (najpewniej na podst. napotkanych aplikacji) ktore maja zupelnie rozne charakterystyki i "najlepsze" rozwiazania dla kazdego zostosowanie jest inne.

# jakubin said on kwietnia 22, 2009 23:03:

@Wojciech: Może zdarzyć się, iż utworzenie obiektu danego singletona nie jest możliwe w każdym momencie (bo np. wymaga informacji konfiguracyjnych, które jeszcze nie zostały załadowane). Oczywiście, trudno sobie wyobrazić taką sytuację w dobrze zaprojektowanym systemie.

Jednakże dyskutujemy tutaj o wzorcu singletona jako takim, więc analizując za i przeciw trzeba wziąć pod uwagę każdą, nawet najgłupszą implementację konstruktora:

if (new Random().Next(2) == 0)

   throw new Exception("Bad luck");

Dlatego uważam, że w tym kontekście nie ma przykładów trafionych i nietrafionych.

# Gutek said on kwietnia 22, 2009 23:26:

@Wojtek

Ad1) Zadalem pytanie, bo nie znalem odpowiedzi (% moglbys odpowiedziec co?;) ), ale skoro chcesz abym wytlumaczyl to prosze. Normalnie jak dostajesz sie poprzez property do instancji to statujesz tak zwany lazy initialization. Powoduje to ze moze sie zdazyc przypdek ze w jednym moemencie zostana utworzone dwie instancje klasy, co jednoznacznie mowi ze to juz nie jest singleton.

Dlatego tez przewaznie stosowany byl lock z tak zwanym double checkiem (o czym masz dosc sporo w linkach ktore juz wyzej podalem wiec nie bede wchodzil wszczegoly).

Jednakze jak teraz przeczytalem, .NET frameowork zapewnia thread safty na statycznych inicjalizatorach, wiec przyklad % bedzie poprawny w systemach wielowatkowych. Dodatkowo tez bedzie tak zwany lazy initiation czyli instancja klasy powstanie dopiero wtedy kiedy ktos sie odowla do property - zgodnie z zalozeniami (to znaczy, statyczny konstruktor powinien zostac wywolany tylko wtedy kiedy nastepuje pierwsza inicjalizacja obiektu, lub kiedy nastepuje odwolani do zmiennej statycznej), drugi link w komentarzu podaje przyklad V ktory zapewni full lazy initialization.

Tu masz przyklady GoFa:

http://www.dofactory.com/Patterns/PatternSingleton.aspx

Dodatkowo przyklad takze masz w linku ktorym podalem:

http://www.yoda.arachsys.com/csharp/singleton.html

Ad2) Ogolnie dalem to dla jaj, ale widze ze to wziales do siebie. a zas co do "nie na temat" to na temat, czyba ze post nie jest o singletonach ;)

Zas co do przykladu Binka.

To przyklad trafiony. % powiedzial o tym jak skorcic pattern signleton, zas Biniek pozakal kiedy taki skrocony nie zadziala. Czyli tak jak napisales u mnie w Ad2 to "trzeba myslec".  Przyklad % jest niezly, ale nie sprawdzi sie w kazdym w kazdym mozliwym przypadku. Wiec wszystko zalezy od tego co czego chcesz uzyc Singletona oraz jakie operacje musisz wykonac w trakcie jego inicjalizacji, a potrzeby moga byc rozne.

# dotnetomaniak.pl said on kwietnia 23, 2009 07:12:

Dziękujemy za publikację - Trackback z dotnetomaniak.pl

# Wojciech Gebczyk said on kwietnia 23, 2009 07:57:

Gutek: dzieki za naprodukowanie sie. chodzili mi o potwierdzenie ze jednak jednolinijkowiec bedzie thread safe (lub null czasami, ale to nie ma znaczenie bo mowimy o jednoinstancyjnosci)

A co do lazy initialization jednolinijkowca to ja bym sie tak nie rozpedzal z tymi twierdzeniami, tylko uwarunkowal "otoczeniem" jednolinijkowca.

Nie rozumieme tego calego szumu ze to nie lazy itp... przeca napisal o tym w wadach nie? - oczywiscie nikt nie zabrania rozpisywania sie w stylu: "potwierdzam dziala <tu powtorzenie w innej formie>"

# simon said on kwietnia 24, 2009 09:17:

Przykład 1 linijkowy jest jak najbardziej poprawnym singletonem. Na 100 Procentów:)

Przykład 18-to linijkowy nie działa. Po pierwsze nie działa dlatego, że zmienna _instance nie jest "volatile". Dlatego nie działa na wielu wątkach. Po drugie nie działa na maszynach IA64, bo tam model pamięci jest jeszcze luźniejszy i, o ile dobrze pamiętam, wymaga memory barrier.

Ja osobiście uważam, że odkąd autorzy kontenerów DI podjęli trud zaimplementowania trybu singletonowego za nas, nie ma sensu powtarzać ich wysiłku;)

Komentarze anonimowe wyłączone

About Procent