Inni już opisywali różnicę między Disposei Finalize(przy okazji Finalizemetoda ta w specyfikacji języka wciąż nazywa się destruktorem), więc dodam tylko trochę o scenariuszach, w których ta Finalizemetoda jest przydatna.
Niektóre typy zawierają zasoby jednorazowe w taki sposób, że można je łatwo wykorzystać i zutylizować za jednym razem. Ogólne użycie jest często takie: otwieranie, czytanie lub pisanie, zamykanie (usuwanie). Bardzo dobrze pasuje do usingkonstrukcji.
Inne są nieco trudniejsze. WaitEventHandlesponieważ instancje nie są używane w ten sposób, ponieważ służą do sygnalizowania z jednego wątku do drugiego. Powstaje zatem pytanie, kto powinien je wezwać Dispose? Jako typy zabezpieczeń takie jak te implementują Finalizemetodę, która zapewnia, że zasoby są usuwane, gdy aplikacja nie odwołuje się już do instancji.
Nie mogłem zrozumieć tej zatwierdzonej odpowiedzi. Nadal chcę wiedzieć inaczej. Co to jest?
Ismael,
22
@Ismael: Największą sytuacją, w której Finalizemożna uzasadnić, jest istnienie pewnej liczby obiektów, które są zainteresowane utrzymywaniem zasobu przy życiu, ale nie ma sposobu, w jaki obiekt, który przestaje być zainteresowany zasobem, może dowiedzieć się, czy jest to ostatni. W takim przypadku Finalizezwykle strzela tylko wtedy, gdy nikt nie jest zainteresowany tym obiektem. Luźny czas Finalizejest okropny dla zasobów nie zamienialnych, takich jak pliki i blokady, ale może być odpowiedni dla zasobów zamiennych.
supercat
13
+1 do supercat za świetne nowe (dla mnie) słowo. Kontekst wyjaśnił to całkiem jasno, ale tak na wszelki wypadek dla reszty z nas, oto co wikipedia mówi: „Fungibility jest własnością towaru lub towaru, którego poszczególne jednostki są zdolne do wzajemnej zamiany, np. Słodkiej ropy naftowej, udziałów w firma, obligacje, metale szlachetne lub waluty ”.
Jon Coombs
5
@JonCoombs: To całkiem słuszne, chociaż warto zauważyć, że termin „zasób zamienny” stosuje się do rzeczy, które można dowolnie zastępować, dopóki nie zostaną nabyte i które ponownie będą swobodnie zastępowalne po zwolnieniu lub rezygnacji . Jeśli w systemie znajduje się pula obiektów zamka, a kod pobiera taki, który kojarzy z jakąś jednostką, to dopóki ktoś trzyma odniesienie do tej blokady w celu skojarzenia jej z tą jednostką , zamka tego nie można zastąpić jakikolwiek inny. Jeśli jednak cały kod, który troszczy się o strzeżoną istotę, porzuci blokadę, ...
supercat
... to znów stałoby się swobodnie zastępowalne, dopóki nie zostanie powiązane z jakimś innym bytem.
supercat
135
Metoda finalizatora jest wywoływana, gdy obiekt jest odśmiecany i nie ma żadnej gwarancji, kiedy to nastąpi (możesz go wymusić, ale pogorszy to wydajność).
Z Disposedrugiej strony metoda powinna być wywoływana przez kod, który utworzył twoją klasę, abyś mógł wyczyścić i zwolnić wszystkie pozyskane zasoby (niezarządzane dane, połączenia z bazą danych, uchwyty plików itp.) W momencie, gdy kod zostanie zakończony twój przedmiot.
Standardowa praktyka jest wdrożenie IDisposablei Disposetak, że można korzystać z obiektu w usingstatment. Takich jak using(var foo = new MyObject()) { }. A w finalizatorze dzwonisz Dispose, na wypadek, gdyby kod telefoniczny zapomniał się ciebie pozbyć.
Należy zachować ostrożność podczas wywoływania opcji Usuń z implementacji Finalizuj - Dispose może także pozbywać się zarządzanych zasobów, których nie chcesz dotykać w finalizatorze, ponieważ mogły one już zostać sfinalizowane.
itowlson,
6
@itowlson: Sprawdzanie wartości null w połączeniu z założeniem, że obiekty mogą być usuwane dwukrotnie (z drugim wywołaniem nie rób nic) powinno być wystarczająco dobre.
Samuel
7
Wygląda na to, że standardowy wzorzec IDisposal i ukryta implementacja Dispose (bool) do obsługi usuwania zarządzanych komponentów są opcjonalne.
Brody,
Wygląda na to, że nie ma powodu, aby implementować destruktor (metoda ~ MyClass ()), a raczej zawsze implementować i wywoływać metodę Dispose (). A może się mylę? Czy ktoś mógłby mi podać przykład, kiedy oba powinny zostać zaimplementowane?
dpelisek
66
Finalizacja jest metodą ochronną, wywoływaną przez moduł odśmiecający, gdy odzyskuje obiekt. Dispose to metoda „deterministycznego czyszczenia”, wywoływana przez aplikacje w celu uwolnienia cennych zasobów natywnych (uchwytów okien, połączeń z bazą danych itp.), Gdy nie są już potrzebne, zamiast pozostawiania ich na czas nieokreślony, dopóki GC nie podejdzie do obiektu.
Jako użytkownik obiektu zawsze używasz Dispose. Finalizacja jest dla GC.
Jako osoba wdrażająca klasę, jeśli posiadasz zarządzane zasoby, które powinny zostać usunięte, implementujesz Dispose. Jeśli dysponujesz zasobami natywnymi, implementujesz zarówno Dispose, jak i Finalize, i oba wywołujesz wspólną metodę, która uwalnia zasoby natywne. Te idiomy są zwykle łączone za pomocą prywatnej metody Dispose (bool disposing), która eliminuje wywołania z wartością true i finalizuje wywołania z wartością false. Ta metoda zawsze uwalnia zasoby rodzime, a następnie sprawdza parametr usuwania, a jeśli jest to prawda, usuwa zasoby zarządzane i wywołuje GC.SuppressFinalize.
Pierwotny zalecany wzorzec dla klas, które zawierały mieszankę zasobów samoczyszczących („zarządzanych”) i nieoczyszczających się („niezarządzanych”), od dawna jest przestarzały. Lepszym wzorcem jest osobne zawijanie każdego niezarządzanego zasobu do własnego obiektu zarządzanego, który nie zawiera żadnych silnych odniesień do niczego, co nie jest konieczne do jego oczyszczenia. Wszystko, do czego sfinalizowany obiekt ma bezpośrednie lub pośrednie silne odniesienie, będzie miało wydłużony czas życia GC. Hermetyzacja rzeczy potrzebnych do czyszczenia pozwoli uniknąć przedłużenia życia GC rzeczy, które nie są.
supercat
2
@JCoombs: Disposejest dobry, a poprawne wdrożenie jest ogólnie łatwe. Finalizejest złe, a jego prawidłowe wdrożenie jest na ogół trudne. Między innymi, ponieważ GC zapewni, że tożsamość żadnego obiektu nigdy nie zostanie „przetworzona”, dopóki istnieje odniesienie do tego obiektu, łatwo jest wyczyścić kilka Disposableobiektów, z których niektóre mogły już zostać oczyszczone, nie ma problemu; każde odniesienie do obiektu, do którego Disposejuż zostało wywołane, pozostanie odniesieniem do obiektu, do którego Disposejuż zostało wywołane.
supercat
2
@JCoombs: Natomiast niezarządzane zasoby zasadniczo nie mają takiej gwarancji. Jeśli obiekt Fredjest właścicielem uchwytu pliku # 42 i go zamyka, system może dołączyć ten sam numer do uchwytu pliku, który jest przekazywany innej jednostce. W takim przypadku uchwyt pliku nr 42 nie odnosiłby się do zamkniętego pliku Freda, ale do pliku, który był aktywnie używany przez ten inny byt; dlaFred próby ścisłej rączką nr 42 ponownie byłyby katastrofalne. Próba 100% niezawodnego śledzenia, czy jeden niezarządzany obiekt został jeszcze zwolniony, jest wykonalna. Próba śledzenia wielu obiektów jest znacznie trudniejsza.
supercat
2
@JCoombs: Jeśli każdy niezarządzany zasób zostanie umieszczony w swoim własnym obiekcie opakowania, który nie kontroluje tylko jego żywotności, wówczas kod zewnętrzny, który nie wie, czy zasób został zwolniony, ale wie, że powinien być, jeśli jeszcze nie był , może bezpiecznie poprosić obiekt opakowania o jego zwolnienie; obiekt opakowania będzie wiedział, czy to zrobił, i może wykonać lub zignorować żądanie. Fakt, że GC gwarantuje, że odniesienie do opakowania zawsze będzie prawidłowym odniesieniem do opakowania, jest bardzo przydatną gwarancją.
supercat
43
Sfinalizować
Finalizatory powinno być zawsze protected, nie publicbądź privatetak, że metoda ta może być wywołana z kodu aplikacji bezpośrednio iw tym samym czasie, może zadzwonić do base.Finalizemetody
Finaliści powinni zwolnić tylko niezarządzane zasoby.
Ramy nie gwarantują, że finalizator w ogóle wykona się w dowolnej instancji.
Nigdy nie przydzielaj pamięci w finalizatorach ani nie wywoływaj wirtualnych metod z finalizatorów.
Unikaj synchronizacji i zgłaszania nieobsługiwanych wyjątków w finalizatorach.
Kolejność wykonywania finalizatorów jest niedeterministyczna - innymi słowy, nie można polegać na tym, że inny obiekt jest nadal dostępny w finalizatorze.
Nie definiuj finalizatorów dla typów wartości.
Nie twórz pustych destruktorów. Innymi słowy, nigdy nie powinieneś jawnie definiować destruktora, chyba że twoja klasa musi oczyścić niezarządzane zasoby, a jeśli go zdefiniujesz, powinien wykonać trochę pracy. Jeśli później nie będziesz już musiał czyścić niezarządzanych zasobów w destruktorze, usuń go całkowicie.
Dysponować
Wprowadzić w życie IDisposable na każdym typie, który ma finalizator
Upewnij się, że obiekt nie nadaje się do użytku po wywołaniu Disposemetody. Innymi słowy, unikaj używania obiektu poDispose wywołaniu metody.
Zadzwoń Disposena wszystkie IDisposabletypy, gdy skończysz z nimi
Pozwól Disposena wielokrotne wywoływanie bez zgłaszania błędów.
Pomiń późniejsze wywołania do finalizatora z poziomu Disposemetody przy użyciu tej GC.SuppressFinalizemetody
Unikaj tworzenia typów wartości jednorazowego użytku
Unikaj rzucania wyjątków w ramach Disposemetod
Usuń / sfinalizowany wzór
Microsoft zaleca wdrożenie obu Disposei Finalizepodczas pracy z niezarządzanymi zasobami. FinalizeRealizacja będzie biegać i zasoby wciąż być uwalniane, gdy obiekt jest garbage zebrane nawet jeśli deweloper zaniedbane wywołać Disposemetodę jawnie.
Oczyść niezarządzane zasoby w Finalizemetodzie oraz Disposemetodzie. Dodatkowo wywołaj Disposemetodę dla wszystkich obiektów .NET, które masz jako komponenty w tej klasie (posiadające niezarządzane zasoby jako ich element członkowski) z Disposemetody.
Istnieje kilka kluczy z książki MCSD Certification Toolkit (egzamin 70-483) str. 193:
destruktor ≈ (jest prawie równy)base.Finalize() , Destruktor jest konwertowany na nadpisaną wersję metody Finalize, która wykonuje kod destruktora, a następnie wywołuje metodę Finalize klasy bazowej. Wtedy jest to całkowicie niedeterministyczne, że nie możesz wiedzieć, kiedy zostanie wywołany, ponieważ zależy od GC.
Jeśli klasa nie zawiera zasobów zarządzanych ani zasobów niezarządzanych , nie powinna implementować IDisposableani mieć destruktora.
Jeśli klasa zarządzała tylko zasobami , powinna zaimplementować, IDisposableale nie powinna mieć destruktora. (Podczas działania destruktora nie można mieć pewności, że zarządzane obiekty nadal istnieją, więc i tak nie można wywoływać ich Dispose()metod).
Jeśli klasa ma tylko niezarządzane zasoby , musi zaimplementować IDisposablei potrzebuje destruktora na wypadek, gdyby program nie zadzwonił Dispose().
Dispose()Metoda musi być bezpieczna, aby uruchomić więcej niż jeden raz. Możesz to osiągnąć, używając zmiennej do śledzenia, czy była wcześniej uruchamiana.
Dispose()powinien uwolnić zasoby zarządzane i niezarządzane .
Destruktor powinien zwolnić tylko niezarządzane zasoby . Podczas działania destruktora nie można mieć pewności, że zarządzane obiekty nadal istnieją, więc i tak nie można wywoływać ich metod Dispose. Uzyskuje się to poprzez zastosowanie protected void Dispose(bool disposing)wzorca kanonicznego , w którym tylko zasoby zarządzane są zwalniane (usuwane), kiedy disposing == true.
Po zwolnieniu zasobów Dispose()należy wywołaćGC.SuppressFinalize , aby obiekt mógł pominąć kolejkę finalizacji.
Przykład implementacji klasy z zasobami niezarządzanymi i zarządzanymi:
using System;classDisposableClass:IDisposable{// A name to keep track of the object.publicstringName="";// Free managed and unmanaged resources.publicvoidDispose(){FreeResources(true);// We don't need the destructor because// our resources are already freed.
GC.SuppressFinalize(this);}// Destructor to clean up unmanaged resources// but not managed resources.~DisposableClass(){FreeResources(false);}// Keep track if whether resources are already freed.privateboolResourcesAreFreed=false;// Free resources.privatevoidFreeResources(bool freeManagedResources){Console.WriteLine(Name+": FreeResources");if(!ResourcesAreFreed){// Dispose of managed resources if appropriate.if(freeManagedResources){// Dispose of managed resources here.Console.WriteLine(Name+": Dispose of managed resources");}// Dispose of unmanaged resources here.Console.WriteLine(Name+": Dispose of unmanaged resources");// Remember that we have disposed of resources.ResourcesAreFreed=true;}}}
To miła odpowiedź! Ale myślę, że to źle: „destruktor powinien wywołać GC.SuppressFinalize”. Zamiast tego, czy publiczna metoda Dispose () nie powinna wywoływać GC.SuppressFinalize? Zobacz: docs.microsoft.com/en-us/dotnet/api/… Wywołanie tej metody zapobiega wywoływaniu przez moduł czyszczenia pamięci obiektu Object.Finalize (który jest zastępowany przez destruktor).
Ewa
7
W 99% przypadków nie powinieneś się tym martwić. :) Ale jeśli twoje obiekty zawierają odniesienia do niezarządzanych zasobów (na przykład uchwytów okien, uchwytów plików), musisz zapewnić sposób, w jaki zarządzany obiekt może zwolnić te zasoby. Finalizacja daje domyślną kontrolę nad uwalnianiem zasobów. Jest wywoływany przez śmietnik. Dispose to sposób na wyraźną kontrolę nad uwolnieniem zasobów i można go wywoływać bezpośrednio.
Na temat Garbage Collection można dowiedzieć się znacznie więcej , ale to dopiero początek.
Jestem prawie pewien, więcej niż 1% C # aplikacji korzystać z baz danych: gdzie muszą się martwić o IDisposable SQL rzeczy.
Samuel
1
Powinieneś również zaimplementować IDisposable, jeśli obudujesz IDisposables. Co prawdopodobnie obejmuje pozostałe 1%.
Darren Clark,
@Samuel: Nie wiem, co bazy danych mają z tym wspólnego. Jeśli mówisz o zamykaniu połączeń, to w porządku, ale to inna sprawa. Nie musisz pozbywać się obiektów w celu zamknięcia połączeń w odpowiednim czasie.
JP Alioto,
1
@JP: Ale wzorzec Używanie (...) znacznie ułatwia radzenie sobie.
Brody,
2
Zgadzam się, ale o to właśnie chodzi. Wykorzystany wzór ukrywa dla ciebie wezwanie do usunięcia.
JP Alioto,
6
Finalizator służy do niejawnego czyszczenia - należy go używać za każdym razem, gdy klasa zarządza zasobami, które absolutnie muszą zostać oczyszczone, ponieważ w przeciwnym razie wyciekłyby uchwyty / pamięć itp.
Prawidłowe wdrożenie finalizatora jest notorycznie trudne i powinno się go unikać wszędzie tam, gdzie to możliwe - SafeHandleklasa (dostępna w .Net v2.0 i nowszych) oznacza teraz, że bardzo rzadko (jeśli w ogóle) trzeba zaimplementować finalizator.
IDisposableInterfejs jest za wyraźną czyszczenia i jest znacznie bardziej powszechnie używane - należy użyć tego, aby użytkownicy mogli wyraźnie zwolnić zasoby oczyszczania lub gdy ich zakończeniu używania obiektu.
Zauważ, że jeśli masz finalizator, powinieneś również zaimplementować IDisposableinterfejs, aby umożliwić użytkownikom jawne zwolnienie tych zasobów wcześniej niż byłoby to w przypadku, gdyby obiekt był odśmiecany.
Piszesz finalizator dla swojej klasy, jeśli odwołuje się on do niezarządzanych zasobów i chcesz się upewnić, że te niezarządzane zasoby zostaną zwolnione, gdy instancja tej klasy zostanie automatycznie wyrzucona
. Zauważ, że nie możesz jawnie wywołać finalizatora obiektu - jest on automatycznie wywoływany przez moduł wyrzucający elementy bezużyteczne, gdy uzna to za konieczne.
Z drugiej strony implementujesz interfejs IDisposable (i w konsekwencji definiujesz metodę Dispose () jako wynik dla swojej klasy), gdy twoja klasa ma odwołanie do niezarządzanych zasobów, ale nie chcesz czekać, aż moduł śmieciowy się uruchomi (który może być w dowolnym momencie - bez kontroli programisty) i chcesz zwolnić te zasoby, gdy tylko skończysz. W ten sposób można jawnie zwolnić niezarządzane zasoby, wywołując metodę Dispose () obiektu.
Kolejna różnica polega na tym, że - w implementacji Dispose () powinieneś również zwolnić zasoby zarządzane , podczas gdy nie należy tego robić w finalizatorze. Wynika to z faktu, że bardzo prawdopodobne jest, że zasoby zarządzane, do których odwołuje się obiekt, zostały już wyczyszczone, zanim będzie gotowe do sfinalizowania.
W przypadku klasy, która korzysta z niezarządzanych zasobów, najlepszą praktyką jest zdefiniowanie zarówno metody Dispose (), jak i finalizatora, które mają być używane jako awaryjne na wypadek, gdyby programista zapomniał jawnie pozbyć się obiektu. Oba mogą korzystać ze wspólnej metody czyszczenia zarządzanych i niezarządzanych zasobów:
classClassWithDisposeAndFinalize:IDisposable{// Used to determine if Dispose() has already been called, so that the finalizer// knows if it needs to clean up unmanaged resources.privatebool disposed =false;publicvoidDispose(){// Call our shared helper method.// Specifying "true" signifies that the object user triggered the cleanup.CleanUp(true);// Now suppress finalization to make sure that the Finalize method // doesn't attempt to clean up unmanaged resources.
GC.SuppressFinalize(this);}privatevoidCleanUp(bool disposing){// Be sure we have not already been disposed!if(!this.disposed){// If disposing equals true i.e. if disposed explicitly, dispose all // managed resources.if(disposing){// Dispose managed resources.}// Clean up unmanaged resources here.}
disposed =true;}// the below is called the destructor or Finalizer~ClassWithDisposeAndFinalize(){// Call our shared helper method.// Specifying "false" signifies that the GC triggered the cleanup.CleanUp(false);}
Zróżnicuj metody finalizowania i usuwania w języku C #.
GC wywołuje metodę finalizacji w celu odzyskania niezarządzanych zasobów (takich jak operacja na plikach, interfejs API systemu Windows, połączenie sieciowe, połączenie z bazą danych), ale czas nie jest ustalony, kiedy GC je wywoła. Nazywa się to niejawnie przez GC, co oznacza, że nie mamy nad tym kontroli niskiego poziomu.
Metoda usuwania: Mamy nad tym kontrolę niskiego poziomu, gdy wywołujemy go z kodu. możemy odzyskać niezarządzane zasoby, ilekroć uznamy, że nie są one użyteczne. Możemy to osiągnąć poprzez wdrożenie wzorca IDisposal.
Instancje klas często obejmują kontrolę nad zasobami, które nie są zarządzane przez środowisko wykonawcze, takie jak uchwyty okien (HWND), połączenia z bazą danych itp. Dlatego należy zapewnić zarówno jawny, jak i niejawny sposób na uwolnienie tych zasobów. Zapewnij niejawną kontrolę, implementując chronioną metodę Finalize na obiekcie (składnia destruktora w C # i Managed Extensions dla C ++). Garbage collector wywołuje tę metodę w pewnym momencie, gdy nie ma już żadnych poprawnych odniesień do obiektu. W niektórych przypadkach możesz chcieć zapewnić programistom korzystającym z obiektu możliwość jawnego zwolnienia tych zasobów zewnętrznych, zanim śmieciarz zwolni obiekt. Jeśli zasoby zewnętrzne są ograniczone lub kosztowne, lepszą wydajność można osiągnąć, jeśli programista wyraźnie zwolni zasoby, gdy nie są już używane. Aby zapewnić jawną kontrolę, zaimplementuj metodę Dispose zapewnianą przez interfejs IDisposable. Konsument obiektu powinien wywołać tę metodę, gdy zakończy się to przy użyciu obiektu. Dispose można wywołać, nawet jeśli istnieją inne odwołania do obiektu.
Zauważ, że nawet jeśli zapewnisz jawną kontrolę za pomocą Dispose, powinieneś zapewnić niejawne czyszczenie przy użyciu metody Finalize. Finalize zapewnia kopię zapasową, aby zapobiec trwałemu wyciekowi zasobów, jeśli programista nie wywoła Dispose.
Główną różnicą między Dispose i Finalize jest to, że:
Disposejest zwykle wywoływany przez twój kod. Zasoby są uwalniane natychmiast po wywołaniu. Ludzie zapominają o wywołaniu metody, więc using() {}wymyślono tę instrukcję. Kiedy twój program zakończy wykonywanie kodu wewnątrz {}, wywoła Disposemetodę automatycznie.
Finalizenie jest wywoływany przez twój kod. Ma to być wywoływane przez Garbage Collector (GC). Oznacza to, że zasób może zostać w dowolnym momencie zwolniony, ilekroć GC zdecyduje się to zrobić. Kiedy GC wykona swoją pracę, przejdzie wiele metod finalizacji. Jeśli masz w tym ciężką logikę, spowoduje to spowolnienie procesu. Może to powodować problemy z wydajnością twojego programu. Uważaj więc na to, co tam umieścisz.
Osobiście większość logiki niszczenia zapisałbym w Dispose. Mam nadzieję, że to wyjaśni zamieszanie.
Jak wiemy, usuwanie i finalizacja oba służą do uwolnienia niezarządzanych zasobów .. ale różnica polega na tym, że finalizacja używa dwóch cykli do uwolnienia zasobów, podczas gdy do usuwania używa jednego cyklu.
Usuń natychmiast zwalnia zasób . Finalizacja może, ale nie musi, uwalniać zasobu z dowolnym stopniem terminowości.
supercat,
1
Ach, to prawdopodobnie oznacza to „a finalizable obiekt musi zostać wykryty przez GC dwukrotnie przed jego pamięć zostanie odebrane”, przeczytać więcej tutaj: ericlippert.com/2015/05/18/...
aeroson
-4
Aby odpowiedzieć na pierwszą część, powinieneś podać przykłady, w których ludzie stosują inne podejście do dokładnie tego samego obiektu klasy. W przeciwnym razie trudno (lub nawet dziwnie) odpowiedzieć.
Innymi słowy: GC wie tylko o finalizatorze (jeśli taki istnieje. Znany również jako destructor dla Microsoft). Dobry kod podejmie próbę wyczyszczenia z obu (finalizatora i usuwania).
Odpowiedzi:
Inni już opisywali różnicę między
Dispose
iFinalize
(przy okazjiFinalize
metoda ta w specyfikacji języka wciąż nazywa się destruktorem), więc dodam tylko trochę o scenariuszach, w których taFinalize
metoda jest przydatna.Niektóre typy zawierają zasoby jednorazowe w taki sposób, że można je łatwo wykorzystać i zutylizować za jednym razem. Ogólne użycie jest często takie: otwieranie, czytanie lub pisanie, zamykanie (usuwanie). Bardzo dobrze pasuje do
using
konstrukcji.Inne są nieco trudniejsze.
WaitEventHandles
ponieważ instancje nie są używane w ten sposób, ponieważ służą do sygnalizowania z jednego wątku do drugiego. Powstaje zatem pytanie, kto powinien je wezwaćDispose
? Jako typy zabezpieczeń takie jak te implementująFinalize
metodę, która zapewnia, że zasoby są usuwane, gdy aplikacja nie odwołuje się już do instancji.źródło
Finalize
można uzasadnić, jest istnienie pewnej liczby obiektów, które są zainteresowane utrzymywaniem zasobu przy życiu, ale nie ma sposobu, w jaki obiekt, który przestaje być zainteresowany zasobem, może dowiedzieć się, czy jest to ostatni. W takim przypadkuFinalize
zwykle strzela tylko wtedy, gdy nikt nie jest zainteresowany tym obiektem. Luźny czasFinalize
jest okropny dla zasobów nie zamienialnych, takich jak pliki i blokady, ale może być odpowiedni dla zasobów zamiennych.Metoda finalizatora jest wywoływana, gdy obiekt jest odśmiecany i nie ma żadnej gwarancji, kiedy to nastąpi (możesz go wymusić, ale pogorszy to wydajność).
Z
Dispose
drugiej strony metoda powinna być wywoływana przez kod, który utworzył twoją klasę, abyś mógł wyczyścić i zwolnić wszystkie pozyskane zasoby (niezarządzane dane, połączenia z bazą danych, uchwyty plików itp.) W momencie, gdy kod zostanie zakończony twój przedmiot.Standardowa praktyka jest wdrożenie
IDisposable
iDispose
tak, że można korzystać z obiektu wusing
statment. Takich jakusing(var foo = new MyObject()) { }
. A w finalizatorze dzwoniszDispose
, na wypadek, gdyby kod telefoniczny zapomniał się ciebie pozbyć.źródło
Finalizacja jest metodą ochronną, wywoływaną przez moduł odśmiecający, gdy odzyskuje obiekt. Dispose to metoda „deterministycznego czyszczenia”, wywoływana przez aplikacje w celu uwolnienia cennych zasobów natywnych (uchwytów okien, połączeń z bazą danych itp.), Gdy nie są już potrzebne, zamiast pozostawiania ich na czas nieokreślony, dopóki GC nie podejdzie do obiektu.
Jako użytkownik obiektu zawsze używasz Dispose. Finalizacja jest dla GC.
Jako osoba wdrażająca klasę, jeśli posiadasz zarządzane zasoby, które powinny zostać usunięte, implementujesz Dispose. Jeśli dysponujesz zasobami natywnymi, implementujesz zarówno Dispose, jak i Finalize, i oba wywołujesz wspólną metodę, która uwalnia zasoby natywne. Te idiomy są zwykle łączone za pomocą prywatnej metody Dispose (bool disposing), która eliminuje wywołania z wartością true i finalizuje wywołania z wartością false. Ta metoda zawsze uwalnia zasoby rodzime, a następnie sprawdza parametr usuwania, a jeśli jest to prawda, usuwa zasoby zarządzane i wywołuje GC.SuppressFinalize.
źródło
Dispose
jest dobry, a poprawne wdrożenie jest ogólnie łatwe.Finalize
jest złe, a jego prawidłowe wdrożenie jest na ogół trudne. Między innymi, ponieważ GC zapewni, że tożsamość żadnego obiektu nigdy nie zostanie „przetworzona”, dopóki istnieje odniesienie do tego obiektu, łatwo jest wyczyścić kilkaDisposable
obiektów, z których niektóre mogły już zostać oczyszczone, nie ma problemu; każde odniesienie do obiektu, do któregoDispose
już zostało wywołane, pozostanie odniesieniem do obiektu, do któregoDispose
już zostało wywołane.Fred
jest właścicielem uchwytu pliku # 42 i go zamyka, system może dołączyć ten sam numer do uchwytu pliku, który jest przekazywany innej jednostce. W takim przypadku uchwyt pliku nr 42 nie odnosiłby się do zamkniętego pliku Freda, ale do pliku, który był aktywnie używany przez ten inny byt; dlaFred
próby ścisłej rączką nr 42 ponownie byłyby katastrofalne. Próba 100% niezawodnego śledzenia, czy jeden niezarządzany obiekt został jeszcze zwolniony, jest wykonalna. Próba śledzenia wielu obiektów jest znacznie trudniejsza.Sfinalizować
protected
, niepublic
bądźprivate
tak, że metoda ta może być wywołana z kodu aplikacji bezpośrednio iw tym samym czasie, może zadzwonić dobase.Finalize
metodyDysponować
IDisposable
na każdym typie, który ma finalizatorDispose
metody. Innymi słowy, unikaj używania obiektu poDispose
wywołaniu metody.Dispose
na wszystkieIDisposable
typy, gdy skończysz z nimiDispose
na wielokrotne wywoływanie bez zgłaszania błędów.Dispose
metody przy użyciu tejGC.SuppressFinalize
metodyDispose
metodUsuń / sfinalizowany wzór
Dispose
iFinalize
podczas pracy z niezarządzanymi zasobami.Finalize
Realizacja będzie biegać i zasoby wciąż być uwalniane, gdy obiekt jest garbage zebrane nawet jeśli deweloper zaniedbane wywołaćDispose
metodę jawnie.Finalize
metodzie orazDispose
metodzie. Dodatkowo wywołajDispose
metodę dla wszystkich obiektów .NET, które masz jako komponenty w tej klasie (posiadające niezarządzane zasoby jako ich element członkowski) zDispose
metody.źródło
Finalizacja zostaje wywołana przez GC, gdy ten obiekt nie jest już używany.
Dispose to zwykła metoda, którą użytkownik tej klasy może wywołać w celu zwolnienia dowolnych zasobów.
Jeśli użytkownik zapomniał zadzwonić Dispose i jeśli klasa ma zaimplementowaną funkcję Finalize, GC upewni się, że zostanie wywołana.
źródło
Istnieje kilka kluczy z książki MCSD Certification Toolkit (egzamin 70-483) str. 193:
destruktor ≈ (jest prawie równy)
base.Finalize()
, Destruktor jest konwertowany na nadpisaną wersję metody Finalize, która wykonuje kod destruktora, a następnie wywołuje metodę Finalize klasy bazowej. Wtedy jest to całkowicie niedeterministyczne, że nie możesz wiedzieć, kiedy zostanie wywołany, ponieważ zależy od GC.Jeśli klasa nie zawiera zasobów zarządzanych ani zasobów niezarządzanych , nie powinna implementować
IDisposable
ani mieć destruktora.Jeśli klasa zarządzała tylko zasobami , powinna zaimplementować,
IDisposable
ale nie powinna mieć destruktora. (Podczas działania destruktora nie można mieć pewności, że zarządzane obiekty nadal istnieją, więc i tak nie można wywoływać ichDispose()
metod).Jeśli klasa ma tylko niezarządzane zasoby , musi zaimplementować
IDisposable
i potrzebuje destruktora na wypadek, gdyby program nie zadzwoniłDispose()
.Dispose()
Metoda musi być bezpieczna, aby uruchomić więcej niż jeden raz. Możesz to osiągnąć, używając zmiennej do śledzenia, czy była wcześniej uruchamiana.Dispose()
powinien uwolnić zasoby zarządzane i niezarządzane .Destruktor powinien zwolnić tylko niezarządzane zasoby . Podczas działania destruktora nie można mieć pewności, że zarządzane obiekty nadal istnieją, więc i tak nie można wywoływać ich metod Dispose. Uzyskuje się to poprzez zastosowanie
protected void Dispose(bool disposing)
wzorca kanonicznego , w którym tylko zasoby zarządzane są zwalniane (usuwane), kiedydisposing == true
.Po zwolnieniu zasobów
Dispose()
należy wywołaćGC.SuppressFinalize
, aby obiekt mógł pominąć kolejkę finalizacji.Przykład implementacji klasy z zasobami niezarządzanymi i zarządzanymi:
źródło
W 99% przypadków nie powinieneś się tym martwić. :) Ale jeśli twoje obiekty zawierają odniesienia do niezarządzanych zasobów (na przykład uchwytów okien, uchwytów plików), musisz zapewnić sposób, w jaki zarządzany obiekt może zwolnić te zasoby. Finalizacja daje domyślną kontrolę nad uwalnianiem zasobów. Jest wywoływany przez śmietnik. Dispose to sposób na wyraźną kontrolę nad uwolnieniem zasobów i można go wywoływać bezpośrednio.
Na temat Garbage Collection można dowiedzieć się znacznie więcej , ale to dopiero początek.
źródło
Finalizator służy do niejawnego czyszczenia - należy go używać za każdym razem, gdy klasa zarządza zasobami, które absolutnie muszą zostać oczyszczone, ponieważ w przeciwnym razie wyciekłyby uchwyty / pamięć itp.
Prawidłowe wdrożenie finalizatora jest notorycznie trudne i powinno się go unikać wszędzie tam, gdzie to możliwe -
SafeHandle
klasa (dostępna w .Net v2.0 i nowszych) oznacza teraz, że bardzo rzadko (jeśli w ogóle) trzeba zaimplementować finalizator.IDisposable
Interfejs jest za wyraźną czyszczenia i jest znacznie bardziej powszechnie używane - należy użyć tego, aby użytkownicy mogli wyraźnie zwolnić zasoby oczyszczania lub gdy ich zakończeniu używania obiektu.Zauważ, że jeśli masz finalizator, powinieneś również zaimplementować
IDisposable
interfejs, aby umożliwić użytkownikom jawne zwolnienie tych zasobów wcześniej niż byłoby to w przypadku, gdyby obiekt był odśmiecany.Zobacz : Aktualizacja DG: Pozbywanie się, finalizacja i zarządzanie zasobami, które uważam za najlepszy i najbardziej kompletny zestaw zaleceń dotyczących finalizatorów i
IDisposable
.źródło
Podsumowanie to -
Kolejna różnica polega na tym, że - w implementacji Dispose () powinieneś również zwolnić zasoby zarządzane , podczas gdy nie należy tego robić w finalizatorze. Wynika to z faktu, że bardzo prawdopodobne jest, że zasoby zarządzane, do których odwołuje się obiekt, zostały już wyczyszczone, zanim będzie gotowe do sfinalizowania.
W przypadku klasy, która korzysta z niezarządzanych zasobów, najlepszą praktyką jest zdefiniowanie zarówno metody Dispose (), jak i finalizatora, które mają być używane jako awaryjne na wypadek, gdyby programista zapomniał jawnie pozbyć się obiektu. Oba mogą korzystać ze wspólnej metody czyszczenia zarządzanych i niezarządzanych zasobów:
źródło
Najlepszy przykład, jaki znam.
źródło
Zróżnicuj metody finalizowania i usuwania w języku C #.
GC wywołuje metodę finalizacji w celu odzyskania niezarządzanych zasobów (takich jak operacja na plikach, interfejs API systemu Windows, połączenie sieciowe, połączenie z bazą danych), ale czas nie jest ustalony, kiedy GC je wywoła. Nazywa się to niejawnie przez GC, co oznacza, że nie mamy nad tym kontroli niskiego poziomu.
Metoda usuwania: Mamy nad tym kontrolę niskiego poziomu, gdy wywołujemy go z kodu. możemy odzyskać niezarządzane zasoby, ilekroć uznamy, że nie są one użyteczne. Możemy to osiągnąć poprzez wdrożenie wzorca IDisposal.
źródło
Instancje klas często obejmują kontrolę nad zasobami, które nie są zarządzane przez środowisko wykonawcze, takie jak uchwyty okien (HWND), połączenia z bazą danych itp. Dlatego należy zapewnić zarówno jawny, jak i niejawny sposób na uwolnienie tych zasobów. Zapewnij niejawną kontrolę, implementując chronioną metodę Finalize na obiekcie (składnia destruktora w C # i Managed Extensions dla C ++). Garbage collector wywołuje tę metodę w pewnym momencie, gdy nie ma już żadnych poprawnych odniesień do obiektu. W niektórych przypadkach możesz chcieć zapewnić programistom korzystającym z obiektu możliwość jawnego zwolnienia tych zasobów zewnętrznych, zanim śmieciarz zwolni obiekt. Jeśli zasoby zewnętrzne są ograniczone lub kosztowne, lepszą wydajność można osiągnąć, jeśli programista wyraźnie zwolni zasoby, gdy nie są już używane. Aby zapewnić jawną kontrolę, zaimplementuj metodę Dispose zapewnianą przez interfejs IDisposable. Konsument obiektu powinien wywołać tę metodę, gdy zakończy się to przy użyciu obiektu. Dispose można wywołać, nawet jeśli istnieją inne odwołania do obiektu.
Zauważ, że nawet jeśli zapewnisz jawną kontrolę za pomocą Dispose, powinieneś zapewnić niejawne czyszczenie przy użyciu metody Finalize. Finalize zapewnia kopię zapasową, aby zapobiec trwałemu wyciekowi zasobów, jeśli programista nie wywoła Dispose.
źródło
Główną różnicą między Dispose i Finalize jest to, że:
Dispose
jest zwykle wywoływany przez twój kod. Zasoby są uwalniane natychmiast po wywołaniu. Ludzie zapominają o wywołaniu metody, więcusing() {}
wymyślono tę instrukcję. Kiedy twój program zakończy wykonywanie kodu wewnątrz{}
, wywołaDispose
metodę automatycznie.Finalize
nie jest wywoływany przez twój kod. Ma to być wywoływane przez Garbage Collector (GC). Oznacza to, że zasób może zostać w dowolnym momencie zwolniony, ilekroć GC zdecyduje się to zrobić. Kiedy GC wykona swoją pracę, przejdzie wiele metod finalizacji. Jeśli masz w tym ciężką logikę, spowoduje to spowolnienie procesu. Może to powodować problemy z wydajnością twojego programu. Uważaj więc na to, co tam umieścisz.Osobiście większość logiki niszczenia zapisałbym w Dispose. Mam nadzieję, że to wyjaśni zamieszanie.
źródło
Jak wiemy, usuwanie i finalizacja oba służą do uwolnienia niezarządzanych zasobów .. ale różnica polega na tym, że finalizacja używa dwóch cykli do uwolnienia zasobów, podczas gdy do usuwania używa jednego cyklu.
źródło
Aby odpowiedzieć na pierwszą część, powinieneś podać przykłady, w których ludzie stosują inne podejście do dokładnie tego samego obiektu klasy. W przeciwnym razie trudno (lub nawet dziwnie) odpowiedzieć.
Jeśli chodzi o drugie pytanie, lepiej najpierw przeczytaj to Prawidłowe użycie interfejsu IDisposable, który to twierdzi
Innymi słowy: GC wie tylko o finalizatorze (jeśli taki istnieje. Znany również jako destructor dla Microsoft). Dobry kod podejmie próbę wyczyszczenia z obu (finalizatora i usuwania).
źródło