Finalizza vs Elimina


Risposte:


121

Altri hanno già coperto la differenza tra Disposee Finalize(tra l'altro il Finalizemetodo è ancora chiamato distruttore nelle specifiche del linguaggio), quindi aggiungerò solo un po 'di scenari in cui il Finalizemetodo è utile.

Alcuni tipi incapsulano le risorse usa e getta in un modo in cui è facile da usare e le smaltiscono in una sola azione. L'uso generale è spesso così: apri, leggi o scrivi, chiudi (Disponi). Si adatta molto bene al usingcostrutto.

Altri sono un po 'più difficili. WaitEventHandlesad esempio, non vengono utilizzati in questo modo in quanto vengono utilizzati per segnalare da un thread all'altro. La domanda allora diventa chi dovrebbe fare appello Disposea questi? Come tipi di protezione come questi implementano un Finalizemetodo, che assicura che le risorse vengano eliminate quando all'applicazione non viene più fatto riferimento.


60
Non riuscivo a capire questa risposta approvata. Voglio ancora sapere il diverso. Cos'è?
Ismael,

22
@Ismael: la situazione più grande in cui Finalizepuò essere giustificata è quando ci sono un certo numero di oggetti che sono interessati ad avere una risorsa mantenuta in vita, ma non c'è modo in cui un oggetto che cessa di essere interessato alla risorsa possa scoprire se è il l'ultimo. In tal caso, di Finalizesolito sparerà solo quando nessuno è interessato all'oggetto. Il tempismo lento di Finalizeè orribile per risorse non fungibili come file e blocchi, ma può essere giusto per risorse fungibili.
supercat,

13
+1 a supercat per una nuova grande (per me) parola. Il contesto lo ha reso abbastanza chiaro, ma nel caso di tutti noi, ecco cosa dice Wikipedia: "La fungibilità è proprietà di un bene o di un prodotto le cui singole unità sono in grado di sostituirsi reciprocamente, come il greggio dolce, partecipa a una società, obbligazioni, metalli preziosi o valute. "
Jon Coombs il

5
@JonCoombs: È praticamente giusto, anche se può valere la pena notare che il termine "risorsa fungibile" viene applicato a cose che sono liberamente sostituibili fino a quando non vengono acquisite e diventano nuovamente sostituibili liberamente dopo il rilascio o l'abbandono . Se il sistema ha un pool di oggetti lock e il codice ne acquisisce uno che associa a qualche entità, fino a quando qualcuno mantiene quel riferimento a quel lock allo scopo di associarlo a quell'entità , quel lock non può essere sostituito con qualsiasi altro. Se tutto il codice che ha a cuore l'entità protetta abbandona la serratura, tuttavia ...
supercat

... quindi sarebbe di nuovo liberamente sostituibile fino al momento in cui è associato con qualche altra entità.
supercat

135

Il metodo finalizzatore viene chiamato quando il tuo oggetto viene garbage collection e non hai alcuna garanzia quando ciò accadrà (puoi forzarlo, ma danneggerà le prestazioni).

Il Disposemetodo d'altra parte è destinato a essere chiamato dal codice che ha creato la classe in modo da poter pulire e liberare tutte le risorse che avete acquisito (dati non gestiti, le connessioni al database, file handle, ecc) il momento il codice è fatto con il tuo oggetto.

La pratica standard è implementare IDisposablee in Disposemodo che tu possa usare il tuo oggetto in una usingdichiarazione. Come using(var foo = new MyObject()) { }. E nel tuo finalizzatore, chiami Dispose, nel caso in cui il codice chiamante abbia dimenticato di eliminarti.


17
Devi essere un po 'attento a chiamare Dispose dall'implementazione di Finalize. Dispose può anche disporre di risorse gestite, che non desideri toccare dal tuo finalizzatore, poiché potrebbero essere già state finalizzate.
itowlson,

6
@itowlson: Il controllo di null combinato con l'assunto che gli oggetti possono essere eliminati due volte (con la seconda chiamata che non fa nulla) dovrebbe essere abbastanza buono.
Samuel,

7
Il modello IDisposal standard e l'implementazione nascosta di un Dispose (bool) per gestire l'eliminazione dei componenti gestiti opzionali sembrano soddisfare tale problema.
Brody,

Sembra che non ci siano motivi per implementare il distruttore (il metodo ~ MyClass ()) e piuttosto implementare e chiamare sempre il metodo Dispose (). O mi sbaglio? Qualcuno potrebbe darmi un esempio quando entrambi dovrebbero essere implementati?
Dpelisek,

66

Finalize è il metodo backstop, chiamato dal garbage collector quando recupera un oggetto. Dispose è il metodo di "pulizia deterministica", chiamato dalle applicazioni per rilasciare preziose risorse native (handle di finestre, connessioni al database, ecc.) Quando non sono più necessarie, piuttosto che lasciarle trattenute indefinitamente fino a quando il GC si avvicina all'oggetto.

Come utente di un oggetto, usi sempre Dispose. Finalize è per il GC.

Come implementatore di una classe, se si possiedono risorse gestite che devono essere eliminate, si implementa Dispose. Se si detengono risorse native, si implementano sia Dispose che Finalize ed entrambi chiamano un metodo comune che rilascia le risorse native. Questi idiomi sono in genere combinati attraverso un metodo Dispose privato (bool disposing), che Dispose chiama con true e Finalizza le chiamate con false. Questo metodo libera sempre le risorse native, quindi controlla il parametro disposing e, se è vero, elimina le risorse gestite e chiama GC.SuppressFinalize.



2
Il modello originale raccomandato per le classi che contenevano un mix di risorse autopulenti ("gestite") e non autopulenti ("non gestite") è stato a lungo obsoleto. Un modello migliore consiste nell'avvolgere separatamente ogni risorsa non gestita nel proprio oggetto gestito che non contiene alcun riferimento forte a tutto ciò che non è necessario per la sua pulizia. Tutto ciò a cui un oggetto finalizzabile contiene un riferimento forte diretto o indiretto avrà una durata GC estesa. Incapsulare le cose necessarie per la pulizia eviterà di prolungare la durata del GC delle cose che non lo sono.
supercat,

2
@JCoombs: Disposeè buono e implementarlo correttamente è generalmente facile. Finalizeè malvagio, e attuarlo correttamente è generalmente difficile. Tra le altre cose, poiché il GC garantirà che l'identità di nessun oggetto venga mai "riciclata" fintanto che esiste un riferimento a quell'oggetto, è facile ripulire un mucchio di Disposableoggetti, alcuni dei quali potrebbero essere già stati ripuliti, è nessun problema; qualsiasi riferimento a un oggetto su cui Disposeè già stato chiamato rimarrà un riferimento a un oggetto su cui Disposeè già stato richiamato.
supercat,

2
@JCoombs: le risorse non gestite, al contrario, generalmente non hanno tale garanzia. Se l'oggetto Fredpossiede l'handle di file n. 42 e lo chiude, il sistema potrebbe associare lo stesso numero a un handle di file fornito a un'altra entità. In tal caso, l'handle del file n. 42 non si riferirebbe al file chiuso di Fred, ma al file che era in uso attivo da quell'altra entità; per Fredprovare a chiudere nuovamente la maniglia # 42 sarebbe disastroso. Cercare di tenere traccia in modo affidabile del 100% se un oggetto non gestito è stato ancora rilasciato è praticabile. Cercare di tenere traccia di più oggetti è molto più difficile.
supercat,

2
@JCoombs: se ogni risorsa non gestita viene inserita nel proprio oggetto wrapper che non fa altro che controllarne la durata, allora il codice esterno che non sa se la risorsa è stata rilasciata, ma sa che dovrebbe essere se non lo fosse già , può tranquillamente chiedere all'oggetto wrapper di rilasciarlo; l'oggetto wrapper saprà se lo ha fatto e può eseguire o ignorare la richiesta. Il fatto che il GC garantisca che un riferimento al wrapper sarà sempre un riferimento valido al wrapper è una garanzia molto utile .
supercat,

43

Finalizza

  • Finalizzatori dovrebbero sempre essere protected, non publico privatein modo che il metodo non può essere chiamato dal codice dell'applicazione direttamente e allo stesso tempo, si può effettuare una chiamata albase.Finalize metodo
  • I finalizzatori devono rilasciare solo risorse non gestite.
  • Il framework non garantisce che un finalizzatore verrà eseguito su una determinata istanza.
  • Non allocare mai memoria nei finalizzatori o chiamare metodi virtuali dai finalizzatori.
  • Evita la sincronizzazione e solleva eccezioni non gestite nei finalizzatori.
  • L'ordine di esecuzione dei finalizzatori non è deterministico, in altre parole, non è possibile fare affidamento su un altro oggetto ancora disponibile nel finalizzatore.
  • Non definire i finalizzatori per i tipi di valore.
  • Non creare distruttori vuoti. In altre parole, non dovresti mai definire esplicitamente un distruttore a meno che la tua classe non abbia bisogno di ripulire risorse non gestite e se ne definisci uno, dovrebbe fare un po 'di lavoro. Se, in seguito, non è più necessario ripulire le risorse non gestite nel distruttore, rimuoverle del tutto.

Smaltire

  • Implementare IDisposablesu ogni tipo che ha un finalizzatore
  • Assicurarsi che un oggetto sia reso inutilizzabile dopo aver effettuato una chiamata al Disposemetodo. In altre parole, evitare di usare un oggetto dopo che il Disposemetodo è stato chiamato su di esso.
  • Chiama Disposetutti i IDisposabletipi una volta che hai finito con loro
  • Consentire Disposedi essere chiamato più volte senza generare errori.
  • Sopprime le chiamate successive al finalizzatore dall'interno del Disposemetodo utilizzando il GC.SuppressFinalizemetodo
  • Evitare di creare tipi di valore usa e getta
  • Evitare di generare eccezioni all'interno dei Disposemetodi

Dispose / modello finalizzato

  • Microsoft consiglia di implementare entrambi Disposee Finalizequando si lavora con risorse non gestite. L' Finalizeimplementazione verrebbe eseguita e le risorse verrebbero comunque rilasciate quando l'oggetto viene garbage collection anche se uno sviluppatore trascura di chiamare il Disposemetodo esplicitamente.
  • Pulizia delle risorse non gestite nel Finalizemetodo e nel Disposemetodo. Inoltre, chiama il Disposemetodo per tutti gli oggetti .NET che hai come componenti all'interno di quella classe (con risorse non gestite come loro membro) dal Disposemetodo.

17
Ho letto la stessa risposta ovunque e ancora non riesco a capire quale sia lo scopo di ciascuno. Ho letto solo le regole dopo le regole, niente di più.
Ismael,

@Ismael: e anche l'autore non aggiunge nulla tranne che per copiare e incollare del testo da MSDN.
Tarik,

@tarik L'ho già imparato. Avevo il concetto di "promessa" quella volta che l'ho chiesto.
Ismael,

31

Finalize viene chiamato dal GC quando questo oggetto non è più in uso.

Dispose è solo un metodo normale che l'utente di questa classe può chiamare per rilasciare qualsiasi risorsa.

Se l'utente ha dimenticato di chiamare Dispose e se la classe ha Finalize implementato, GC si assicurerà che venga chiamato.


3
La risposta più pulita di sempre
dariogriffo,

19

Ci sono alcune chiavi sul libro MCSD Certification Toolkit (esame 70-483) pag 193:

destructor ≈ (è quasi uguale a)base.Finalize() , Il distruttore viene convertito in una versione di override del metodo Finalize che esegue il codice del distruttore e quindi chiama il metodo Finalize della classe base. Quindi è totalmente non deterministico che non puoi sapere quando verrà chiamato perché dipende da GC.

Se una classe non contiene risorse gestite e risorse non gestite , non dovrebbe implementare IDisposableo avere un distruttore.

Se la classe ha gestito solo risorse , dovrebbe implementarsi IDisposablema non dovrebbe avere un distruttore. (Quando il distruttore viene eseguito, non si può essere sicuri che esistano ancora oggetti gestiti, quindi non è possibile chiamare Dispose()comunque i loro metodi.)

Se la classe ha solo risorse non gestite , deve implementare IDisposablee necessita di un distruttore nel caso in cui il programma non chiami Dispose().

Dispose()il metodo deve essere sicuro per essere eseguito più di una volta. Puoi farlo utilizzando una variabile per tenere traccia del fatto che sia stata eseguita in precedenza.

Dispose()dovrebbe liberare risorse gestite e non gestite .

Il distruttore dovrebbe liberare solo risorse non gestite . Quando il distruttore viene eseguito, non si può essere sicuri che esistano ancora oggetti gestiti, quindi non è possibile chiamare comunque i loro metodi Dispose. Ciò si ottiene utilizzando il protected void Dispose(bool disposing)modello canonico , in cui solo le risorse gestite vengono liberate (eliminate) quando disposing == true.

Dopo aver liberato le risorse, Dispose()dovrebbe chiamareGC.SuppressFinalize , in modo che l'oggetto possa saltare la coda di finalizzazione.

Un esempio di un'implementazione per una classe con risorse non gestite e gestite:

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

2
Questa è una bella risposta! Ma penso che sia sbagliato: "il distruttore dovrebbe chiamare GC.SuppressFinalize". Invece, il metodo Dispose () pubblico non dovrebbe chiamare GC.SuppressFinalize? Vedere: docs.microsoft.com/en-us/dotnet/api/… La chiamata a questo metodo impedisce al garbage collector di chiamare Object.Finalize (che viene sovrascritto dal distruttore).
Ewa,

7

Il 99% delle volte, non dovresti preoccuparti di nessuno dei due. :) Ma, se i tuoi oggetti contengono riferimenti a risorse non gestite (handle di finestre, handle di file, ad esempio), devi fornire un modo per far sì che l'oggetto gestito rilasci tali risorse. Finalize offre un controllo implicito sul rilascio di risorse. Viene chiamato dal Garbage Collector. Dispose è un modo per dare un controllo esplicito su un rilascio di risorse e può essere chiamato direttamente.

C'è molto di più da imparare sull'argomento della Garbage Collection , ma è un inizio.


5
Sono abbastanza sicuro che oltre l'1% delle applicazioni C # utilizzare banche dati: dove dovete preoccupare di cose IDisposable SQL.
Samuel,

1
Inoltre, è necessario implementare IDisposable se si incapsula IDisposable. Che probabilmente copre l'altro 1%.
Darren Clark,

@ Samuel: non vedo che cosa hanno a che fare i database con esso. Se stai parlando di chiudere le connessioni, va bene, ma è una questione diversa. Non è necessario disporre oggetti per chiudere le connessioni in modo tempestivo.
JP Alioto,

1
@JP: Ma il modello Using (...) lo rende molto più semplice da gestire.
Brody,

2
D'accordo, ma questo è esattamente il punto. Il modello di utilizzo nasconde la chiamata a Dispose per te.
JP Alioto,

6

Il finalizzatore è per la pulizia implicita: dovresti usarlo ogni volta che una classe gestisce risorse che devono assolutamente essere ripulite, altrimenti perderai handle / memoria ecc ...

L'implementazione corretta di un finalizzatore è notoriamente difficile e dovrebbe essere evitata laddove possibile - la SafeHandleclasse (disponibile in .Net v2.0 e successive) ora significa che molto raramente (se mai) è necessario implementare un finalizzatore più.

Il IDisposable interfaccia è per la pulizia esplicita ed è molto più comunemente usata: è necessario utilizzarla per consentire agli utenti di rilasciare o pulire esplicitamente le risorse ogni volta che hanno finito di usare un oggetto.

Si noti che se si dispone di un finalizzatore, è necessario implementare anche l' IDisposableinterfaccia per consentire agli utenti di rilasciare esplicitamente tali risorse prima di quanto sarebbero se l'oggetto fosse garbage collection.

Vedi DG Update: Dispose, Finalization e Resource Management per quello che considero il migliore e più completo insieme di raccomandazioni su finalizzatori e IDisposable.


3

Il riassunto è -

  • Scrivi un finalizzatore per la tua classe se fa riferimento a risorse non gestite e vuoi assicurarti che tali risorse non gestite vengano rilasciate quando un'istanza di quella classe viene raccolta automaticamente . Nota che non puoi chiamare il Finalizzatore di un oggetto in modo esplicito: viene chiamato automaticamente dal Garbage Collector come e quando lo ritiene necessario.
  • D'altra parte, si implementa l'interfaccia IDisposable (e di conseguenza si definisce il metodo Dispose () come risultato per la propria classe) quando la propria classe fa riferimento a risorse non gestite, ma non si desidera attendere l'avvio del garbage collector (che può essere in qualsiasi momento - non sotto il controllo del programmatore) e desideri rilasciare tali risorse non appena hai finito. Pertanto, è possibile rilasciare esplicitamente risorse non gestite chiamando il metodo Dispose () di un oggetto.

Inoltre, un'altra differenza è: nell'implementazione di Dispose (), è necessario rilasciare anche le risorse gestite , mentre ciò non dovrebbe essere fatto in Finalizer. Questo perché è molto probabile che le risorse gestite a cui fa riferimento l'oggetto siano già state ripulite prima che sia pronto per essere finalizzato.

Per una classe che utilizza risorse non gestite, la migliore pratica è definire sia il metodo Dispose () che il Finalizzatore - da utilizzare come fallback nel caso in cui uno sviluppatore dimentichi di eliminare esplicitamente l'oggetto. Entrambi possono utilizzare un metodo condiviso per ripulire le risorse gestite e non gestite: -

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

2

Il miglior esempio che conosco.

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

2

Diff tra i metodi Finalize e Dispose in C #.

GC chiama il metodo finalize per recuperare le risorse non gestite (come operarion file, api windows, connessione di rete, connessione al database) ma il tempo non è fisso quando GC lo chiamerebbe. Viene chiamato implicitamente da GC, il che significa che non abbiamo un controllo di basso livello su di esso.

Metodo di eliminazione: abbiamo un controllo di basso livello su di esso come lo chiamiamo dal codice. possiamo recuperare le risorse non gestite ogni volta che riteniamo che non sia utilizzabile. Possiamo raggiungere questo obiettivo implementando il modello IDisposal.


1

Le istanze di classe spesso incapsulano il controllo su risorse che non sono gestite dal runtime, come handle di finestra (HWND), connessioni al database e così via. Pertanto, è necessario fornire un modo esplicito e implicito per liberare tali risorse. Fornire il controllo implicito implementando il metodo Finalize protetto su un oggetto (sintassi del distruttore in C # e Managed Extensions per C ++). Il garbage collector chiama questo metodo ad un certo punto dopo che non ci sono più riferimenti validi all'oggetto. In alcuni casi, potresti voler fornire ai programmatori che utilizzano un oggetto la possibilità di rilasciare esplicitamente queste risorse esterne prima che il garbage collector liberi l'oggetto. Se una risorsa esterna è scarsa o costosa, è possibile ottenere prestazioni migliori se il programmatore rilascia esplicitamente risorse quando non vengono più utilizzate. Per fornire un controllo esplicito, implementare il metodo Dispose fornito dall'interfaccia IDisposable. Il consumatore dell'oggetto dovrebbe chiamare questo metodo quando viene fatto usando l'oggetto. Dispose può essere chiamato anche se altri riferimenti all'oggetto sono vivi.

Si noti che anche quando si fornisce un controllo esplicito tramite Dispose, è necessario fornire una pulizia implicita utilizzando il metodo Finalize. Finalize fornisce un backup per impedire la perdita permanente delle risorse se il programmatore non riesce a chiamare Dispose.


1

La principale differenza tra Dispose e Finalize è che:

Disposeviene solitamente chiamato dal tuo codice. Le risorse vengono liberate all'istante quando lo chiami. Le persone dimenticano di chiamare il metodo, quindi using() {}viene inventata la dichiarazione. Quando il programma termina l'esecuzione del codice all'interno di {}, chiamerà il Disposemetodo automaticamente.

Finalizenon viene chiamato dal tuo codice. È inteso per essere chiamato dal Garbage Collector (GC). Ciò significa che la risorsa potrebbe essere liberata in qualsiasi momento in futuro ogni volta che GC decide di farlo. Quando GC farà il suo lavoro, passerà attraverso molti metodi Finalize. Se hai una logica pesante in questo, rallenterà il processo. Potrebbe causare problemi di prestazioni per il tuo programma. Quindi fai attenzione a ciò che hai messo lì.

Personalmente scriverei la maggior parte della logica di distruzione in Dispose. Spero che questo chiarisca la confusione.


-1

Come sappiamo, disporre e finalizzare sono entrambi utilizzati per liberare risorse non gestite .. ma la differenza è finalizzare utilizza due cicli per liberare le risorse, mentre come disporre utilizza un ciclo ..


Dispose libera immediatamente la risorsa . Finalize può o meno liberare la risorsa con qualsiasi grado di tempestività.
Supercat

1
Ah, probabilmente intende questo "un oggetto finalizzabile deve essere rilevato dal GC due volte prima che la sua memoria venga recuperata", leggi di più qui: ericlippert.com/2015/05/18/…
aeroson

-4

Per rispondere nella prima parte, devi fornire esempi in cui le persone usano un approccio diverso per lo stesso oggetto di classe. Altrimenti è difficile (o addirittura strano) rispondere.

Per quanto riguarda la seconda domanda, leggi prima questo uso corretto dell'interfaccia IDisposable che afferma che

È la vostra scelta! Ma scegli Dispose.

In altre parole: il GC conosce solo il finalizzatore (se presente. Conosciuto anche come distruttore di Microsoft). Un buon codice tenterà di ripulire da entrambi (finalizzatore e smaltimento).

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.