Odpowiedzi:
Inni już opisywali różnicę między Dispose
i Finalize
(przy okazji Finalize
metoda ta w specyfikacji języka wciąż nazywa się destruktorem), więc dodam tylko trochę o scenariuszach, w których ta Finalize
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.
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 przypadku Finalize
zwykle strzela tylko wtedy, gdy nikt nie jest zainteresowany tym obiektem. Luźny czas Finalize
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
i Dispose
tak, że można korzystać z obiektu w using
statment. Takich jak using(var foo = new MyObject()) { }
. A w finalizatorze dzwonisz Dispose
, na wypadek, gdyby kod telefoniczny zapomniał się ciebie pozbyć.
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.
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ć kilka Disposable
obiektów, z których niektóre mogły już zostać oczyszczone, nie ma problemu; każde odniesienie do obiektu, do którego Dispose
już zostało wywołane, pozostanie odniesieniem do obiektu, do którego Dispose
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
, nie public
bądź private
tak, że metoda ta może być wywołana z kodu aplikacji bezpośrednio iw tym samym czasie, może zadzwonić do base.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 wszystkie IDisposable
typy, gdy skończysz z nimiDispose
na wielokrotne wywoływanie bez zgłaszania błędów.Dispose
metody przy użyciu tej GC.SuppressFinalize
metodyDispose
metodUsuń / sfinalizowany wzór
Dispose
i Finalize
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 oraz Dispose
metodzie. Dodatkowo wywołaj Dispose
metodę dla wszystkich obiektów .NET, które masz jako komponenty w tej klasie (posiadające niezarządzane zasoby jako ich element członkowski) z Dispose
metody.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.
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ć ich Dispose()
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), 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;
class DisposableClass : IDisposable
{
// A name to keep track of the object.
public string Name = "";
// Free managed and unmanaged resources.
public void Dispose()
{
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.
private bool ResourcesAreFreed = false;
// Free resources.
private void FreeResources(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;
}
}
}
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.
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
.
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:
class ClassWithDisposeAndFinalize : IDisposable
{
// Used to determine if Dispose() has already been called, so that the finalizer
// knows if it needs to clean up unmanaged resources.
private bool disposed = false;
public void Dispose()
{
// 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);
}
private void CleanUp(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);
}
Najlepszy przykład, jaki znam.
public abstract class DisposableType: IDisposable
{
bool disposed = false;
~DisposableType()
{
if (!disposed)
{
disposed = true;
Dispose(false);
}
}
public void Dispose()
{
if (!disposed)
{
disposed = true;
Dispose(true);
GC.SuppressFinalize(this);
}
}
public void Close()
{
Dispose();
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
// managed objects
}
// unmanaged objects and resources
}
}
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:
Dispose
jest 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 Dispose
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.
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.
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
To Twój wybór! Ale wybierz Usuń.
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).