Kiedy powinienem stworzyć destruktor?

185

Na przykład:

public class Person
{
    public Person()
    {
    }

    ~Person()
    {
    }
}

Kiedy powinienem ręcznie utworzyć destruktor? Kiedy musiałeś stworzyć destruktor?

David Heffernan
źródło
1
Język C # nazywa te „destruktorami”, ale większość ludzi nazywa je „finalizatorami”, ponieważ jest to ich nazwa .NET i zmniejsza to pomylenie z destruktorami C ++ (które są zupełnie inne). Jak zaimplementować IDisposable i Finalizery: 3 proste zasady
Stephen Cleary
5
Kiedy czujesz się lekkomyślny.
capdragon
32
Myślę, że klawiatura TomTom działała nieprawidłowo. Caps Lock sporadycznie włączał się i wyłączał. Dziwne.
Jeff LaFay,
patrz także stackoverflow.com/questions/1076965/…
Ian Ringrose
Skończyło się na tym, że użyłem destruktora jako pomocy w debugowaniu opartej na sugestii Grega Beecha: stackoverflow.com/questions/3832911/...
Brian

Odpowiedzi:

237

AKTUALIZACJA: To pytanie było przedmiotem mojego bloga w maju 2015 roku . Dzięki za świetne pytanie! Na blogu znajduje się długa lista kłamstw, które ludzie powszechnie wierzą w finalizację.

Kiedy powinienem ręcznie utworzyć destruktor?

Prawie nigdy.

Zazwyczaj jeden tworzy niszczyciel tylko wtedy, gdy klasa trzyma się jakiegoś drogiego niezarządzanego zasobu, który musi zostać wyczyszczony, gdy obiekt zniknie. Lepiej jest użyć wzorca jednorazowego, aby zapewnić oczyszczenie zasobu. Destruktor jest wówczas zasadniczo gwarancją, że jeśli konsument Twojego obiektu zapomni go zutylizować, zasoby nadal zostaną ostatecznie oczyszczone. (Może.)

Jeśli stworzysz destruktor, zachowaj szczególną ostrożność i zrozum, jak działa śmieciarz . Niszczyciele są naprawdę dziwne :

  • Nie działają na twoim wątku; działają na własnym wątku. Nie powoduj impasu!
  • Nieobsługiwany wyjątek zgłoszony przez destruktor to zła wiadomość. Jest na swoim własnym wątku; kto to złapie?
  • Destruktor może zostać wywołany na obiekcie po uruchomieniu konstruktora, ale przed jego zakończeniem. Prawidłowo napisany destruktor nie będzie polegał na niezmiennikach ustalonych w konstruktorze.
  • Destruktor może „wskrzesić” obiekt, przywracając martwy obiekt do życia. To naprawdę dziwne. Nie rób tego
  • Destruktor może nigdy nie działać; nie można polegać na tym, że obiekt zostanie kiedykolwiek zaplanowany do finalizacji. To prawdopodobnie będzie, ale to nie jest gwarancja.

Prawie nic, co jest normalnie prawdziwe, nie jest prawdą w destruktorze. Bądź naprawdę bardzo ostrożny. Pisanie poprawnego destruktora jest bardzo trudne.

Kiedy musiałeś stworzyć destruktor?

Podczas testowania części kompilatora, która obsługuje destruktory. Nigdy nie musiałem tego robić w kodzie produkcyjnym. Rzadko piszę obiekty, które manipulują niezarządzanymi zasobami.

Eric Lippert
źródło
„Destruktor może zostać wywołany na obiekcie po uruchomieniu konstruktora, ale przed jego zakończeniem.” Ale mogę polegać na inicjatorach pola, aby uruchomić, prawda?
konfigurator
13
@configurator: Nie. Załóżmy, że inicjator trzeciego pola obiektu z finalizatorem o nazwie metoda statyczna spowodował zgłoszenie wyjątku. Kiedy uruchomi się inicjator czwartego pola? Nigdy. Ale obiekt jest nadal przydzielony i musi zostać sfinalizowany. Do diabła, nie masz nawet gwarancji, że pola typu double zostały w pełni zainicjowane podczas działania dtor. W połowie pisania mogło dojść do przerwania wątku, a teraz finalizator musi poradzić sobie z podwójnie zainicjowanym w połowie zerowym podwójnym.
Eric Lippert,
1
Znakomity post, ale powinienem powiedzieć, że „powinien zostać utworzony, gdy twoja klasa trzyma się jakiegoś drogiego niezarządzanego obiektu lub powoduje istnienie dużej liczby niezarządzanych obiektów” - Na konkretny przykład mam klasę macierzy w języku C #, która wykorzystuje podstawowy natywny C ++ klasa macierzy do wykonywania dużych obciążeń - robię dużo matryc - „destruktor” jest znacznie lepszy od IDisposable w tym konkretnym przypadku, ponieważ utrzymuje lepszą synchronizację zarządzanych i niezarządzanych stron domu
Mark Mullin
1
Pythonnet używa destruktora do wydania GIL w niezarządzanym CPython
denfromufa
3
Niesamowity artykuł Eric. Propozycje tego -> „Dodatkowa dodatkowa zabawa: środowisko wykonawcze używa mniej agresywnego generowania kodu i mniej agresywnego zbierania śmieci podczas uruchamiania programu w debuggerze, ponieważ źle się debuguje, gdy obiekty, które debugujesz, nagle znikają, mimo że zmienna odnosząca się do obiektu ma zasięg. Oznacza to, że jeśli masz błąd, w którym obiekt jest finalizowany zbyt wcześnie, prawdopodobnie nie możesz go odtworzyć w debuggerze! ”
Ken Palmer,
17

Nazywa się to „finalizatorem” i zwykle powinieneś utworzyć tylko dla klasy, której stan (tj. Pola) zawiera niezarządzane zasoby (tj. Wskaźniki do uchwytów pobranych za pomocą wywołań p / invoke). Jednak w .NET 2.0 i nowszych istnieje lepszy sposób radzenia sobie z usuwaniem niezarządzanych zasobów: SafeHandle . Biorąc to pod uwagę, prawie nigdy nie powinieneś więcej pisać finalizatora.

Nicole Calinoiu
źródło
25
@ThomasEding - Tak to jest . C # używa składni destruktora, ale w rzeczywistości tworzy finalizator . Ponownie .
JDB wciąż pamięta Monikę
@JDB: Konstrukcja językowa nazywa się destruktorem. Nie podoba mi się nazwa, ale tak się nazywa. Akt deklarowania destruktora powoduje, że kompilator generuje metodę finalizatora, która zawiera trochę kodu opakowania wraz z tym, co pojawia się w treści destruktora.
supercat
8

Nie potrzebujesz go, chyba że klasa utrzymuje niezarządzane zasoby, takie jak uchwyty plików systemu Windows.

John Saunders
źródło
5
Właściwie nazywa się to destruktorem
David Heffernan
2
Teraz jestem zdezorientowany. Czy to finalizator czy niszczyciel?
4
Specyfikacja C # w rzeczywistości nazywa to destruktorem. Niektórzy uważają to za błąd. stackoverflow.com/questions/1872700/…
Ani
2
@ThomasEding - Tak to jest . C # używa składni destruktora, ale w rzeczywistości tworzy finalizator .
JDB wciąż pamięta Monikę
2
Uwielbiam komentarze tutaj, prawdziwe panto :)
Benjol
4

Nazywa się to destruktorem / finalizatorem i zwykle jest tworzony podczas implementacji wzorca Disposed.

Jest to rozwiązanie awaryjne, gdy użytkownik twojej klasy zapomina zadzwonić Dispose, aby upewnić się, że (w końcu) twoje zasoby zostaną uwolnione, ale nie masz żadnej gwarancji, kiedy wywoływany jest destruktor.

W tym pytaniu dotyczącym przepełnienia stosu zaakceptowana odpowiedź poprawnie pokazuje, jak zaimplementować wzorzec usuwania. Jest to potrzebne tylko wtedy, gdy klasa zawiera jakiekolwiek niezasłonięte zasoby, których śmieciarz nie zdoła sam się oczyścić.

Dobrą praktyką jest nie wdrażanie finalizatora bez umożliwienia użytkownikowi klasy ręcznego usunięcia obiektu w celu niezwłocznego uwolnienia zasobów.

Øyvind Bråthen
źródło
Właściwie NIE jest to nazywane destruktorem w języku C # bez uzasadnionego powodu.
TomTom
14
Właściwie to jest . Dzięki, że oddałeś mi głos, ponieważ się mylisz. Zobacz bibliotekę MSDN dotyczącą tego konkretnego problemu: msdn.microsoft.com/en-us/library/66x5fx1b.aspx
Øyvind Bråthen
1
@TomTom to oficjalna nazwa to destruktor
David Heffernan
W rzeczywistości nie jest to metoda rezerwowa, po prostu pozwala GC zarządzać, gdy twoje obiekty zwalniają niezarządzane zasoby, wdrożenie IDisposable pozwala ci zarządzać nim samemu.
HasaniH
3

Gdy masz niezarządzane zasoby i musisz się upewnić, że zostaną one wyczyszczone, gdy obiekt zniknie. Dobrym przykładem mogą być obiekty COM lub programy obsługi plików.

Vlad Bezden
źródło
2

Użyłem destruktora (tylko do celów debugowania), aby sprawdzić, czy obiekt jest usuwany z pamięci w ramach aplikacji WPF. Nie byłem pewien, czy wyrzucanie elementów bezużytecznych naprawdę usuwa obiekt z pamięci, i to był dobry sposób na sprawdzenie.

Neil.Allen
źródło
1

Destruktory zapewniają niejawny sposób na uwolnienie niezarządzanych zasobów zamkniętych w twojej klasie, są wywoływane, gdy GC się do nich zbliża, i domyślnie wywołują metodę Finalize klasy podstawowej. Jeśli korzystasz z wielu niezarządzanych zasobów, lepiej jest zapewnić jawny sposób uwolnienia tych zasobów za pomocą interfejsu IDisposable. Zobacz przewodnik programowania w języku C #: http://msdn.microsoft.com/en-us/library/66x5fx1b.aspx

HasaniH
źródło