Kiedy powinienem używać GC.SuppressFinalize ()?

287

W .NET, w jakich okolicznościach powinienem użyć GC.SuppressFinalize()?

Jakie korzyści daje mi ta metoda?

GEOCHET
źródło
Widziałem kilka pytań na temat finalizatorów i IDisposable, przepełnienie stosu również powinno mieć coś na temat GC.SupressFinalize i słabe referencje
Sam Saffron
Nie sądzę, że słabe referencje mają wiele wspólnego z finalizatorami - być może powinieneś zadać bardziej bezpośrednie pytanie na ich temat.
Michael Burr
Yerp Chciałem opublikować osobne pytanie dotyczące słabych referencji, wszystko to może się ze sobą wiązać podczas budowania pul obiektów. Powinienem również zadać pytanie o odnowienie obiektu al ReRegisterForFinalize
Sam Saffron

Odpowiedzi:

296

SuppressFinalizepowinien być wywoływany tylko przez klasę, która ma finalizator. Informuje Garbage Collector (GC), że thisobiekt został w pełni wyczyszczony.

Zalecany IDisposablewzór w przypadku finalizatora to:

public class MyClass : IDisposable
{
    private bool disposed = false;

    protected virtual void Dispose(bool disposing)
    {
        if (!disposed)
        {
            if (disposing)
            {
                // called via myClass.Dispose(). 
                // OK to use any private object references
            }
            // Release unmanaged resources.
            // Set large fields to null.                
            disposed = true;
        }
    }

    public void Dispose() // Implement IDisposable
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    ~MyClass() // the finalizer
    {
        Dispose(false);
    }
}

Zwykle CLR śledzi obiekty za pomocą finalizatora podczas ich tworzenia (co powoduje, że ich tworzenie jest droższe). SuppressFinalizeinformuje GC, że obiekt został poprawnie wyczyszczony i nie musi wchodzić do kolejki finalizatora. Wygląda jak niszczyciel C ++, ale nie działa jak jeden.

SuppressFinalizeOptymalizacja nie jest trywialne, jak twoi obiekty mogą żyć długo czekać w kolejce finalizatora. Nie ulegaj pokusie, aby przywoływać SuppressFinalizeinne przedmioty. To poważna wada, która czeka.

Wytyczne projektowe informują nas, że finalizator nie jest konieczny, jeśli obiekt jest implementowany IDisposable, ale jeśli masz finalizator, powinieneś wdrożyć go, IDisposableaby umożliwić deterministyczne porządkowanie swojej klasy.

Przez większość czasu powinieneś być w stanie uciec od IDisposableczyszczenia zasobów. Finalizator powinien być potrzebny tylko wtedy, gdy obiekt utrzymuje niezarządzane zasoby i musisz zagwarantować, że te zasoby zostaną oczyszczone.

Uwaga: Czasami koderzy dodają finalizator do debugowania kompilacji własnych IDisposableklas w celu przetestowania, czy kod IDisposablepoprawnie pozbył się swojego obiektu.

public void Dispose() // Implement IDisposable
{
    Dispose(true);
#if DEBUG
    GC.SuppressFinalize(this);
#endif
}

#if DEBUG
~MyClass() // the finalizer
{
    Dispose(false);
}
#endif
Robert Paulson
źródło
1
W pierwszym fragmencie kodu zamieszczam tylko, jak wygląda zalecany wzorzec IDisposable + finalizatora. Kod debugujący jest dobry, ale może rozpraszać. .. Mogę tylko zalecać unikanie finalizatorów, z wyjątkiem klas, które mają niezarządzane zasoby. Pisanie bezpiecznego kodu finalizatora nie jest trywialne.
Robert Paulson
1
Cześć, Dlaczego musimy wywołać dispose z false jako parametr z finalizatora? Co się stanie, jeśli dyspozycja nigdy nie zostanie wezwana, a wtedy nie zostanie sprzedana? Co jeśli po prostu sprawdzimy, czy obiekt został zutylizowany, czy nie i wykonamy faktyczne czyszczenie.
Marzyciel
3
@Dreamer - zależy od twojej implementacji. Zasadniczo chcesz wiedzieć, czy finalizator wywołuje Dispose w porównaniu z implementacją IDisposable.Dispose (). W przypadku wywołania z finalizatora musisz założyć, że prywatne odniesienia nie są już ważne i naprawdę nie możesz wiele zrobić. Jeśli jednak zostanie wywołany z IDisposable.Dispose (), wiesz, że odwołania są nadal aktualne.
Robert Paulson,
32
Jeśli klasa implementująca IDisposablenie jest sealed, powinna zawierać wywołanie, GC.SuppressFinalize(this) nawet jeśli nie zawiera finalizatora zdefiniowanego przez użytkownika . Jest to konieczne, aby zapewnić właściwą semantykę dla typów pochodnych, które dodają finalizator zdefiniowany przez użytkownika, ale tylko zastępują chronioną Dispose(bool)metodę.
Sam Harwell
1
sealedDla klas pochodnych ważne jest, aby nie być wspomnianym przez @SamHarwell. CodeAnalysis daje ok. 1816 + ca1063, gdy klasa nie jest zapieczętowana, ale klasy zapieczętowane są w porządku bez SuppressFinalize.
dashy
38

SupressFinalizeinformuje system, że jakakolwiek praca wykonana w finalizatorze została już wykonana, więc finalizator nie musi być wywoływany. Z dokumentów .NET:

Obiekty, które implementują interfejs IDisposable, mogą wywoływać tę metodę z metody IDisposable.Dispose, aby uniemożliwić śmieciarzowi wywołanie Object.Finalizuj na obiekcie, który go nie wymaga.

Ogólnie rzecz biorąc, większość Dispose()metod powinna być w stanie wywołać GC.SupressFinalize(), ponieważ powinna wyczyścić wszystko, co zostanie wyczyszczone w finalizatorze.

SupressFinalizejest po prostu czymś, co zapewnia optymalizację, która pozwala systemowi nie zawracać sobie głowy kolejkowaniem obiektu do wątku finalizatora. Prawidłowo napisany Dispose()/ finalizator powinien działać poprawnie z lub bez połączenia z GC.SupressFinalize().

Michael Burr
źródło
2

Metodę tę należy wywołać na Disposemetodzie obiektów, która implementuje IDisposable, w ten sposób GC nie wywołałby finalizatora innym razem, gdyby ktoś wywołał Disposemetodę.

Patrz: Metoda GC.SuppressFinalize (Object) - Dokumenty Microsoft

albertein
źródło
9
Myślę, że „Must” jest błędne - nawet „nie powinno” - po prostu w niektórych scenariuszach możesz wyeliminować narzut związany z kolejkowaniem / finalizowaniem obiektu.
Podstawowy
1
Dispose(true);
GC.SuppressFinalize(this);

Jeśli obiekt ma finalizator, .net umieścił odwołanie w kolejce finalizacji.

Ponieważ mamy wywołanie Dispose(ture), jest to jasny obiekt, więc nie potrzebujemy kolejki finalizacji, aby wykonać tę pracę.

Więc wywołaj GC.SuppressFinalize(this)odwołanie odwołania w kolejce finalizacji.

Max CHien
źródło
0

Jeśli klasa, czy coś pochodzi od niego, może trzymać ostatni koncert odniesienie do obiektu z finalizatora, następnie albo GC.SuppressFinalize(this)czy GC.KeepAlive(this)należy wezwać na obiekcie po każdej operacji, które mogłyby mieć negatywny wpływ tego finalizatora, zapewniając w ten sposób, że won finalizator będzie działać do momentu zakończenia tej operacji.

Koszt GC.KeepAlive()i GC.SuppressFinalize(this)są zasadniczo takie same w każdej klasie, która nie ma finalizatora, a klasy, które mają finalizatory, powinny generalnie wywoływać GC.SuppressFinalize(this), więc użycie tej ostatniej funkcji jako ostatniego kroku Dispose()może nie zawsze być konieczne, ale nie będzie mylić się.

supercat
źródło