Wzorzec .NET IDisposable oznacza, że jeśli napiszesz finalizator i zaimplementujesz IDisposable, finalizator musi jawnie wywołać Dispose. Jest to logiczne i zawsze to robiłem w rzadkich sytuacjach, gdy finalizator jest uzasadniony.
Jednak co się stanie, jeśli po prostu to zrobię:
class Foo : IDisposable
{
public void Dispose(){ CloseSomeHandle(); }
}
i nie implementuj finalizatora ani nic takiego. Czy struktura wywoła za mnie metodę Dispose?
Tak, zdaję sobie sprawę, że to brzmi głupio i cała logika sugeruje, że tak nie będzie, ale zawsze miałem dwie rzeczy z tyłu głowy, które sprawiały, że nie byłem pewien.
Ktoś kilka lat temu powiedział mi kiedyś, że rzeczywiście tak się stanie, a ta osoba miała bardzo solidne doświadczenie w „znajomości swoich rzeczy”.
Kompilator / framework wykonuje inne „magiczne” rzeczy w zależności od tego, jakie interfejsy zaimplementujesz (np. Foreach, metody rozszerzające, serializacja oparta na atrybutach itp.), Więc ma sens, że może to być również „magiczne”.
Chociaż czytałem wiele rzeczy na ten temat i było wiele sugestii, nigdy nie byłem w stanie znaleźć ostatecznej odpowiedzi Tak lub Nie na to pytanie.
źródło
Chcę podkreślić punkt widzenia Briana w jego komentarzu, ponieważ jest on ważny.
Finalizatory nie są deterministycznymi destruktorami, jak w C ++. Jak zauważyli inni, nie ma gwarancji, kiedy zostanie wywołana, a jeśli masz wystarczająco dużo pamięci, czy kiedykolwiek zostanie wywołana.
Ale złą rzeczą w finalizatorach jest to, że, jak powiedział Brian, powoduje to, że twój obiekt przetrwa czyszczenie pamięci. To może być złe. Czemu?
Jak pewnie wiesz lub nie, GC jest podzielony na pokolenia - Gen 0, 1 i 2 oraz stertę dużych obiektów. Podział to luźny termin - otrzymujesz jeden blok pamięci, ale są wskazówki, gdzie zaczynają się i kończą obiekty Gen 0.
Proces myślowy jest taki, że prawdopodobnie użyjesz wielu obiektów, które będą krótkotrwałe. Więc to powinno być łatwe i szybkie, aby GC mógł dotrzeć do obiektów Gen 0. Kiedy więc występuje presja pamięci, pierwszą rzeczą, którą robi, jest kolekcja Gen 0.
Teraz, jeśli to nie rozwiązuje wystarczającego nacisku, to wraca i wykonuje zamiatanie Gen 1 (ponawianie Gen 0), a następnie, jeśli nadal nie wystarcza, wykonuje zamiatanie Gen 2 (ponawianie Gen 1 i Gen 0). Dlatego czyszczenie obiektów o długiej żywotności może zająć trochę czasu i być dość kosztowne (ponieważ wątki mogą zostać zawieszone podczas operacji).
Oznacza to, że jeśli zrobisz coś takiego:
Twój obiekt, bez względu na wszystko, będzie żył do generacji 2. Dzieje się tak, ponieważ GC nie ma możliwości wywołania finalizatora podczas czyszczenia pamięci. Tak więc obiekty, które mają zostać sfinalizowane, są przenoszone do specjalnej kolejki, aby zostać wyczyszczone przez inny wątek (wątek finalizujący - który, jeśli zabijesz, powoduje różnego rodzaju złe rzeczy). Oznacza to, że obiekty pozostają dłużej i potencjalnie wymuszają więcej zbierania śmieci.
Tak więc wszystko to ma na celu doprowadzenie do punktu, w którym chcesz użyć IDisposable do czyszczenia zasobów, gdy tylko jest to możliwe, i poważnie spróbować znaleźć sposób na obejście finalizatora. Leży to w najlepszym interesie Twojej aplikacji.
źródło
Jest tu już dużo dobrej dyskusji i trochę się spóźniłem na imprezę, ale sam chciałem dodać kilka punktów.
To prosta wersja, ale istnieje wiele niuansów, które mogą potknąć się o ten wzór.
Moim zdaniem znacznie lepiej jest całkowicie unikać jakichkolwiek typów, które bezpośrednio zawierają zarówno jednorazowe odwołania, jak i zasoby natywne, które mogą wymagać finalizacji. SafeHandles zapewnia bardzo czysty sposób na zrobienie tego poprzez hermetyzację zasobów natywnych do jednorazowych, które wewnętrznie zapewniają własną finalizację (wraz z szeregiem innych korzyści, takich jak usunięcie okna podczas P / Invoke, gdzie natywny uchwyt może zostać utracony z powodu asynchronicznego wyjątku) .
Po prostu zdefiniowanie SafeHandle sprawia, że ta trywialna:
Pozwala uprościć typ zawierający:
źródło
GC.SuppressFinalize
w tym przykładzie. W tym kontekście SuppressFinalize powinien być wywoływany tylko wtedy, gdy zostanieDispose(true)
wykonany pomyślnie. JeśliDispose(true)
zakończy się niepowodzeniem w pewnym momencie po wstrzymaniu finalizacji, ale zanim wszystkie zasoby (szczególnie niezarządzane) zostaną wyczyszczone, nadal chcesz, aby finalizacja nastąpiła, aby wykonać jak najwięcej czyszczenia. Lepiej przenieśćGC.SuppressFinalize
wywołanie doDispose()
metody po wywołaniu funkcjiDispose(true)
. Zobacz wytyczne dotyczące projektowania ram i ten post .Nie sądzę. Masz kontrolę nad wywołaniem metody Dispose, co oznacza, że teoretycznie możesz napisać kod usuwania, który przyjmuje założenia dotyczące (na przykład) istnienia innych obiektów. Nie masz kontroli nad tym, kiedy wywoływany jest finalizator, więc byłoby nieefektywne, gdyby finalizator automatycznie wywoływał Dispose w Twoim imieniu.
EDYCJA: Odszedłem i przetestowałem, aby się upewnić:
źródło
Nie w przypadku, który opisujesz, ale GC zadzwoni do Finalizatora , jeśli go masz.
JEDNAK. Przy następnym wyrzucaniu elementów bezużytecznych, zamiast zbierania, obiekt przejdzie do kolejki finalizacji, wszystko zostanie zebrane, a następnie zostanie wywołany finalizator. Następna kolekcja zostanie uwolniona.
W zależności od obciążenia pamięci aplikacji przez pewien czas możesz nie mieć gc do generowania tego obiektu. Tak więc w przypadku, powiedzmy, strumienia plików lub połączenia db, być może trzeba będzie chwilę poczekać, aż niezarządzany zasób zostanie przez chwilę zwolniony w wywołaniu finalizatora, powodując pewne problemy.
źródło
Nie, to nie jest wywołane.
Ale to sprawia, że łatwo nie zapomnieć o pozbyciu się przedmiotów. Po prostu użyj
using
słowa kluczowego.Zrobiłem w tym celu następujący test:
źródło
GC nie wywoła dispose. To może wywołać swoją finalizatora, ale nawet to nie jest gwarantowana w każdych okolicznościach.
W tym artykule omówiono najlepsze sposoby radzenia sobie z tym problemem.
źródło
Dokumentacja IDisposable zawiera dość jasne i szczegółowe wyjaśnienie zachowania, a także przykładowy kod. GC NIE wywoła
Dispose()
metody w interfejsie, ale wywoła finalizator dla twojego obiektu.źródło
Wzorzec IDisposable został utworzony głównie w celu wywołania przez dewelopera, jeśli masz obiekt, który implementuje IDispose, deweloper powinien zaimplementować
using
słowo kluczowe wokół kontekstu obiektu lub bezpośrednio wywołać metodę Dispose.Bezpiecznym niepowodzeniem dla wzorca jest zaimplementowanie finalizatora wywołującego metodę Dispose (). Jeśli tego nie zrobisz, możesz spowodować wycieki pamięci, np .: Jeśli utworzysz opakowanie COM i nigdy nie wywołasz System.Runtime.Interop.Marshall.ReleaseComObject (comObject) (który zostałby umieszczony w metodzie Dispose).
W clr nie ma żadnej magii do automatycznego wywoływania metod Dispose, innych niż śledzenie obiektów, które zawierają finalizatory i przechowywanie ich w tabeli Finalizer przez GC i wywoływanie ich, gdy GC uruchomi niektóre heurystyki czyszczenia.
źródło