Quando dovrei usare GC.SuppressFinalize ()?


288

In .NET, in quali circostanze dovrei usare GC.SuppressFinalize()?

Quali vantaggi mi offre questo metodo?


Ho visto alcune domande su finalizzatori e IDisposable, stackoverflow dovrebbe anche avere qualcosa su GC.SupressFinalize e riferimenti deboli
Sam Saffron,

Non credo che i riferimenti deboli facciano molto per quanto riguarda i finalizzatori - forse dovresti pubblicare una domanda più diretta su di loro.
Michael Burr,

Sì, intendevo pubblicare una domanda separata sui ref deboli, tutto ciò può essere collegato quando si creano pool di oggetti. Inoltre, dovrei porre una domanda sul revival dell'oggetto ala ReRegisterForFinalize
Sam Saffron,

Risposte:


297

SuppressFinalizedovrebbe essere chiamato solo da una classe che ha un finalizzatore. Sta informando il Garbage Collector (GC) che l' thisoggetto è stato ripulito completamente.

Lo IDisposableschema consigliato quando si dispone di un finalizzatore è:

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);
    }
}

Normalmente, il CLR tiene sotto controllo gli oggetti con un finalizzatore quando vengono creati (rendendoli più costosi da creare). SuppressFinalizedice al GC che l'oggetto è stato ripulito correttamente e non ha bisogno di andare nella coda del finalizzatore. Sembra un distruttore C ++, ma non si comporta come uno.

L' SuppressFinalizeottimizzazione non è banale, poiché i tuoi oggetti possono vivere a lungo in attesa sulla coda del finalizzatore. Non essere tentato di invocare SuppressFinalizealtri oggetti, attenzione a te. È un grave difetto che attende di accadere.

Le linee guida di progettazione ci informano che un finalizzatore non è necessario se il tuo oggetto viene implementato IDisposable, ma se hai un finalizzatore devi implementarlo IDisposableper consentire la pulizia deterministica della tua classe.

Il più delle volte dovresti essere in grado di cavartela IDisposableper ripulire le risorse. Dovresti aver bisogno di un finalizzatore solo quando il tuo oggetto si attacca a risorse non gestite e devi garantire che tali risorse vengano ripulite.

Nota: a volte i programmatori aggiungono un finalizzatore per eseguire il debug di build delle proprie IDisposableclassi al fine di verificare che il codice abbia disposto IDisposablecorrettamente il proprio oggetto.

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

#if DEBUG
~MyClass() // the finalizer
{
    Dispose(false);
}
#endif

1
Nel primo frammento di codice sto solo pubblicando come appare il modello IDisposable + finalizzatore consigliato. Il codice di debug è buono, ma può essere fonte di distrazione. .. Posso solo consigliare di evitare i finalizzatori tranne che per le classi che hanno risorse non gestite. Scrivere un codice finalizzatore sicuro non è banale.
Robert Paulson,

1
Ciao, perché dobbiamo chiamare dispose con false come parametro dal finalizzatore? Cosa succede se lo smaltimento non è mai stato chiamato e quindi non verrà eliminato? Che cosa succede se controlliamo solo se l'oggetto è stato eliminato o meno e facciamo l'effettiva pulizia.
Dreamer,

3
@Dreamer - dipende dalla tua implementazione. In generale, vuoi sapere se Dispose viene chiamato dal finalizzatore rispetto all'implementazione IDisposable.Dispose (). Se chiamato dal finalizzatore, devi presumere che i riferimenti privati ​​non siano più validi e non puoi davvero fare molto. Se comunque chiamato da IDisposable.Dispose (), sai che i riferimenti sono ancora validi.
Robert Paulson,

32
Se l'implementazione della classe IDisposablenon lo è sealed, dovrebbe includere la chiamata GC.SuppressFinalize(this) anche se non include un finalizzatore definito dall'utente . Ciò è necessario per garantire una semantica corretta per i tipi derivati ​​che aggiungono un finalizzatore definito dall'utente ma sovrascrivono solo il Dispose(bool)metodo protetto .
Sam Harwell

1
Non essere sealedmenzionato da @SamHarwell è importante, per le classi derivate. CodeAnalysis produce ca1816 + ca1063 quando la classe non è sigillata, ma le classi sigillate vanno bene senza SuppressFinalize.
dashesy,

38

SupressFinalizedice al sistema che qualsiasi lavoro sarebbe stato fatto nel finalizzatore è già stato fatto, quindi il finalizzatore non ha bisogno di essere chiamato. Dai documenti .NET:

Gli oggetti che implementano l'interfaccia IDisposable possono chiamare questo metodo dal metodo IDisposable.Dispose per impedire al garbage collector di chiamare Object.Finalize su un oggetto che non lo richiede.

In generale, quasi tutti i Dispose()metodi dovrebbero essere in grado di chiamare GC.SupressFinalize(), perché dovrebbero ripulire tutto ciò che verrebbe ripulito nel finalizzatore.

SupressFinalizeè solo qualcosa che fornisce un'ottimizzazione che consente al sistema di non disturbare l'accodamento dell'oggetto al thread del finalizzatore. Un Dispose()finalizzatore / scritto correttamente dovrebbe funzionare correttamente con o senza una chiamata a GC.SupressFinalize().


2

Quel metodo deve essere chiamato sul Disposemetodo degli oggetti che implementano il IDisposable, in questo modo il GC non chiamerebbe il finalizzatore un'altra volta se qualcuno chiama il Disposemetodo.

Vedere: Metodo GC.SuppressFinalize (Object) - Microsoft Docs


9
Penso che "Must" sia sbagliato - nemmeno "dovrebbe" - È solo che in alcuni scenari, è possibile eliminare il sovraccarico di accodare / finalizzare l'oggetto.
Base

1
Dispose(true);
GC.SuppressFinalize(this);

Se l'oggetto ha un finalizzatore, .net inserisce un riferimento nella coda di finalizzazione.

Dato che abbiamo chiamato Dispose(ture), cancella l'oggetto, quindi non abbiamo bisogno di una coda di finalizzazione per fare questo lavoro.

Quindi chiama GC.SuppressFinalize(this)rimuovi riferimento nella coda di finalizzazione.


0

Se una classe, o qualsiasi cosa derivata da essa, potrebbe contenere l'ultimo riferimento attivo a un oggetto con un finalizzatore, allora uno GC.SuppressFinalize(this)o GC.KeepAlive(this)dovrebbe essere chiamato sull'oggetto dopo qualsiasi operazione che potrebbe essere influenzata negativamente da quel finalizzatore, assicurando così che il finalizzatore abbia vinto fino a quando l'operazione non è stata completata.

Il costo di GC.KeepAlive()e GC.SuppressFinalize(this)sono essenzialmente gli stessi in qualsiasi classe che non ha un finalizzatore, e le classi che hanno finalizzatori dovrebbero generalmente chiamare GC.SuppressFinalize(this), quindi l'uso di quest'ultima funzione come ultimo passaggio Dispose()potrebbe non essere sempre necessario, ma non lo farà sbagliarsi.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.