.NET zawiera klasy przeznaczone do leniwej inicjalizacji i zwykle nie trzeba samemu implementować tego wzorca. Jeśli jakiś obiekt, chcemy inicjalizować dopiero w momencie, gdy jest on potrzebny to można skorzystać ze wspomnianych typów.
Lazy<T> to typ generyczny i wystarczy w konstruktorze przekazać metodę tworzącą obiekt:
var largeObject = new Lazy<LargeObject>(() => { Thread.Sleep(5000); Console.WriteLine("Inicjalizacja..."); return new LargeObject(); }); var instance1 = largeObject.Value; var instance2 = largeObject.Value;
W momencie pierwszego wywołania właściwości Value, obiekt zostanie utworzony, co spowoduje wyświetlenie napisu “Inicjalizacja…”. Kolejne odwołania do właściwości będą już korzystały z tej samej, zbuforowanej wartości. Możliwe jest również stworzenie obiektu Lazy, bez przekazania metody inicjalizacyjnej:
var largeObject = new Lazy<LargeObject>();
W takim przypadku, po prostu domyślny konstruktor zostanie wywołany.
Kolejnym parametrem jaki możemy przekazać w konstruktorze jest isThreadSafe:
var largeObject = new Lazy<LargeObject>(isThreadSafe:false); Task.Factory.StartNew(() => largeObject.Value); Task.Factory.StartNew(() => largeObject.Value); Task.Factory.StartNew(() => largeObject.Value); Console.ReadLine();
Jeśli isThreadSafe ustawiony jest na false, to znaczy, że instancja nie jest bezpieczna z punktu widzenia wielowątkowości. Z tego względu, konstruktor zostanie wywołany osobno w każdym z wątków. Jeśli przekażemy wartość true, wtedy obiekt będzie współdzielony przez wszystkie wątki.
Podobną funkcję pełni parametr LazyThreadSafetyMode:
var largeObject = new Lazy<LargeObject>(LazyThreadSafetyMode.ExecutionAndPublication);
LazyThreadSafetyMode posiada jedną trzech wartości. Można przekazać NONE, co powinno być wykorzystywane wyłącznie, gdy znana jest kolejność inicjalizacji. Jest to najszybszy z dostępnych trybów, ale nie gwarantuje on spójności danych – efekt jest niezdefiniowany. Z kolei ExecutionAndPublication spowoduje ten sam efekt, co isThreadSafe ustawiony na true – wyłącznie jeden obiekt zostanie stworzony. W tym przypadku blokady są wykorzystywane aby zsynchronizować wywołania. PublicationOnly pełni analogiczną funkcję do isThreadSafety false (każdy wątek tworzy własną kopię).
Kolejną klasą, przydatną w środowisku współbieżnym jest LazyInitializer. Zacznijmy od przykładu:
LargeObject largeObject = null; LazyInitializer.EnsureInitialized(ref largeObject); Console.WriteLine(largeObject.GetHashCode()); LazyInitializer.EnsureInitialized(ref largeObject); Console.WriteLine(largeObject.GetHashCode());
Klasa sprawdza, czy obiekt jest już zainicjalizowany. Jeśli tak to po prostu go zwraca. W przeciwnym wypadku zostanie wywołany domyślny konstruktor. Operacja jest oczywiście thread-safe. Za pomocą LazyInitializer możemy w bezpieczny sposób zadbać, że tylko jedna instancja zostanie utworzona w środowisku wielowątkowym.
Tak jak w przypadku Lazy<T> można przekazać własną fabrykę:
LazyInitializer.EnsureInitialized(ref largeObject,()=>new LargeObject());
Nie wiedziałem o istnieniu LazyInitializer. Z drugiej strony nie widzę wiele zastosowania. Pod spodem razczej korzystaja z tego samego mechanizmu, tylko Lazy ładnie to juz opakowuje. Oczywiście są sytuacje, że LazyInitializer jest szybszy, ale wychodzi to dopiero w benchmarkach (mały overhead związany z opakowaniem).
Jako ciekawostka – spoko.