O destruktorach pisałem już kilka razy na blogu. W wielkim skrócie – zawsze należy przemyśleć decyzje o implementacji Finalize ponieważ wiąże to się z spadkiem wydajności (obiekt może być nawet “wypromowany” do drugiej generacji GC). Czasami jednak zachodzi taka potrzeba – głównie w przypadku użycia niezarządzanych zasobów. CriticalFinalizerObject daje nam jeszcze kilka dodatkowych gwarancji. Przed przeczytaniem tego wpisu, polecam zapoznanie się z poprzednim postem o CER.
Co zatem da nam dziedziczenie po CriticalFinalizerObject? Po kolei:
-
Tak samo jak w przypadku CER, kod w Finalize będzie skompilowany dużo wcześniej. Daje to gwarancje, że nie wystąpi OutOfMemoryException. Pamięć jest alokowana z góry co ma pomóc w zagwarantowaniu wywołania FInalize. Pamiętajmy, że w destruktorze najprawdopodobniej umieścimy kod zwalniający zasoby niezarządzane. W przypadku gdyby CLR nie miał wystarczającej pamięci na skompilowanie i wykonanie kodu Finalize, wtedy będziemy mieć do czynienia z wyciekiem pamięci.
-
Wszystkie destruktory obiektów, które dziedziczą po CriticalFinalizerObject, będą wywołane PO wykonaniu Finalize w obiektach niedziedziczących po CriticalFinalizerObject. Dlaczego jest to takie ważne? Dobrym przykładem jest klasa FileStream. Umożliwia ona operacje na plikach a zatem na zasobach niezarządzanych. Z tego względu posiada wskaźnik do kodu niezarządzanego (w formie SafeHandle ale o tym w następnym w wpisie). SafeHandle dziedziczy po CriticalFinalizerObject zatem jego finalizer będzie wykonany przed destruktorem klasy FileStream, która nie dziedziczy po CrtiticalFinalizerObject. W momencie zwalniania zasobów, FileStream może dokonać operacji flush na SafeHandle, ponieważ ma pewność, że SafeHandle nie został jeszcze zwolniony. Jest to konieczne, ponieważ FileStream buforuje dane i nie wykonuje operacji bezpośrednio na plikach(ze względu na wydajność).
-
Finalizer będzie wykonany nawet gdy AppDomain zostanie usunięta z pamięci w sposób nieoczekiwany (głównie przez środowisko uruchomieniowe).
CriticalFinalizerObject ma bardzo ważne zastosowanie w klasie SafeHandle, ale o tym w następnym w poście. W dzisiejszym wpisie chciałem tylko przybliżyć zasadę działania CriticalFinalizerObject. Korzystanie z niego jest bardzo proste, wystarczy dziedziczyć po nim tzn.:
class CerExample:CriticalFinalizerObject { ~CerExample() { Console.WriteLine("~ctor"); } }
One thought on “Implementacja Finalize oraz CriticalFinalizerObject”