2009-09-10 20 views
6

Stavo leggendo l'articolo this l'altro giorno e mi chiedevo perché c'era un Finalizzatore insieme al metodo Dispose. Ho letto here in SO in merito al motivo per cui è possibile aggiungere Dispose al Finalizer. La mia curiosità è, quando sarebbe chiamato il Finalizer sul metodo Dispose stesso? Esiste un esempio di codice o si basa su qualcosa che accade sul sistema in cui è in esecuzione il software? In tal caso, cosa potrebbe accadere di non avere il metodo Dispose eseguito dal GC.Quando non viene richiamato il metodo?

risposta

9

Lo scopo del finaliser qui è semplicemente una misura di sicurezza contro le perdite di memoria (se vi capita non chiamare Dispose esplicitamente). Significa anche che non è necessario disporre gli oggetti se si desidera che rilascino risorse quando il programma si arresta, poiché il GC sarà costretto a finalizzare e raccogliere comunque tutti gli oggetti.

Come punto correlato, è importante disporre l'oggetto in modo leggermente diverso quando si esegue questa operazione dal finalizzatore.

~MyClass() 
{ 
    Dispose(false); 
} 

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

protected void Dispose(disposing) 
{ 
    if (!this.disposed) 
    { 
     if (disposing) 
     { 
      // Dispose managed resources here. 
     } 
     // Dispose unmanaged resources here. 
    } 
    this.disposed = true; 
} 

La ragione per cui non non vogliono smaltire le risorse gestite in finaliser è che si sarebbe in realtà essere la creazione di forti riferimenti a loro in questo modo, e questo potrebbe impedire al GC di fare il suo lavoro correttamente e la raccolta loro. Le risorse non gestite (ad esempio handle Win32 e simili) devono sempre essere esplicitamente chiuse/eliminate, ovviamente, poiché CLR non ne è a conoscenza.

+1

E un altro motivo per non disporre di risorse gestite nel finalizzatore ... È possibile che potrebbero essere già stati convertiti in GC nel momento in cui viene eseguito il finalizzatore. Provare a smaltirli quando sono già stati raccolti causerebbe un errore di runtime. – LukeH

+1

@Luke: true, ma è possibile evitarlo abbastanza facilmente impostando tutti i riferimenti su null e quindi eseguendo un controllo nullo prima dello smaltimento. – Noldorin

+0

@Noldorin - Dove si annulla l'annullamento della registrazione degli eventi nel tuo esempio? Capisco che tecnicamente sarebbero stati gestiti, ma se avessimo qualche oggetto legato a questa classe attraverso un evento e non lo annetteremo alla registrazione nella parte non gestita (supponendo che l'utente non chiami Dispose direttamente e sia lasciato al GC per pulirlo) su). Sarebbe sicuro/ok mettere l'annullamento della registrazione degli eventi nella sezione gestita per assicurarsi che ciò accada? L'effetto collaterale potrebbe essere che qualcuno pensa di eliminare un oggetto, ma in realtà non viene mai eliminato a causa del collegamento tra questa classe e l'altro. – SwDevMan81

4

Questo è principalmente lì per proteggersi. Non puoi dettare ciò che l'utente finale della tua classe farà. Fornendo un finalizzatore oltre a un metodo Dispose, il GC "Dispose" del tuo oggetto, liberando le risorse in modo appropriato, anche se l'utente dimentica di chiamare Dispose() o usa male la tua classe.

+1

Vale la pena ricordare che il GC non è deterministico, quindi non c'è alcuna garanzia di quando, * o anche se *, il tuo finalizzatore verrà chiamato. – LukeH

+0

Sì - Se il programma viene eseguito abbastanza a lungo, è più probabile che il tuo oggetto venga finalizzato. Inoltre, se si spegne in modo pulito, verrà finalizzato. Ma non ci sono garanzie con il GC - che è parte del perché IDisposable esiste in primo luogo. –

1

Il metodo di eliminazione deve essere chiamato in modo esplicito, chiamando Dispose() o avendo l'oggetto in un'istruzione using. Il GC chiamerà sempre il finalizzatore, quindi se c'è qualcosa che deve accadere prima che gli oggetti siano eliminati, il finalizzatore dovrebbe almeno controllare per assicurarsi che tutto nell'oggetto venga ripulito.

Si vuole evitare di pulire gli oggetti nel finalizzatore se possibile, perché provoca un lavoro extra rispetto al loro smaltimento prima della mano (come la chiamata di smaltimento), ma si dovrebbe sempre controllare il finalizzatore se ci sono oggetti disteso che deve essere rimosso.

2

Il Finalizer viene chiamato quando l'oggetto viene sottoposto a garbage collection. Smaltire deve essere chiamato esplicitamente. Nel codice seguente verrà chiamato il finalizzatore, ma il metodo Dispose non lo è.

class Foo : IDisposable 
{ 
    public void Dispose() 
    { 
    Console.WriteLine("Disposed"); 
    } 

    ~Foo() 
    { 
    Console.WriteLine("Finalized"); 
    } 
} 

... 

public void Go() 
{ 
    Foo foo = new Foo(); 
} 
+1

Questo non è completamente vero. Il finalizzatore viene chiamato qualche tempo dopo che l'oggetto sarebbe altrimenti idoneo per la garbage collection (cioè l'applicazione non fa più riferimento all'istanza). Tuttavia, poiché il finalizzatore deve essere eseguito per l'istanza, il CLR effettivamente radica l'oggetto e quindi non viene raccolto in modo automatico finché non viene eseguito il finalizzatore. –

+0

Inoltre, non c'è alcuna garanzia che un oggetto * sarà * mai GC'd o che il suo finalizzatore sarà * mai * chiamato. Ecco perché è doppiamente importante assicurarsi di smaltire correttamente qualsiasi oggetto 'IDisposable'. – LukeH

0

Una nota importante ma sottile non ancora citato: uno scopo raramente considerata di smaltimento è impedire che un oggetto venga ripulito prematuramente. Gli oggetti con finalizzatori devono essere scritti attentamente, per evitare che un finalizzatore venga eseguito in precedenza del previsto. Un finalizzatore non può essere eseguito prima dell'inizio dell'ultima chiamata al metodo che verrà eseguita su un oggetto (*), ma potrebbe talvolta eseguire durante l' l'ultima chiamata al metodo se l'oggetto verrà abbandonato una volta completato il metodo. Il codice che disponga correttamente un oggetto non può abbandonare l'oggetto prima di chiamare Dispose, quindi non vi è alcun pericolo che un finalizzatore causi il caos sul codice che usa correttamente Dispose. D'altra parte, se l'ultimo metodo per utilizzare un oggetto fa uso di entità che verranno ripulite nel finalizzatore dopo l'ultimo utilizzo del riferimento all'oggetto stesso, è possibile che il garbage-collector chiami Finalize sull'oggetto e pulisca su entità che sono ancora in uso.Il rimedio è quello di garantire che qualsiasi metodo di chiamata che utilizza entità che verranno ripulite da un finalizzatore debba essere seguito ad un certo punto da una chiamata al metodo che fa uso di "questo". GC.KeepAlive (questo) è un buon metodo da usare per questo.

(*) I metodi non virtuali che sono estesi al codice in linea che non fa nulla con l'oggetto possono essere esenti da questa regola, ma Dispose di solito è, o invoca, un metodo virtuale.

Problemi correlati