2009-03-10 7 views
50

Quello che segue è un esempio tipico pattern Dispose:Perché chiamare dispose (false) nel distruttore?

public bool IsDisposed { get; private set; } 

    #region IDisposable Members 

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

    protected virtual void Dispose(bool disposing) 
    { 
    if (!IsDisposed) 
    { 
     if (disposing) 
     { 
     //perform cleanup here 
     } 

     IsDisposed = true; 
    } 
    } 

    ~MyObject() 
    { 
    Dispose(false); 
    } 

Capisco quello smaltire fa, ma quello che non capisco è perché si vuole chiamare Dispose (false) nel distruttore? Se guardi la definizione, non farebbe assolutamente nulla, quindi perché qualcuno dovrebbe scrivere un codice come questo? Non avrebbe senso solo chiamare non dal distruttore?

risposta

38

Il finalizzatore è usato come ripiego se l'oggetto non è disposto correttamente per qualche motivo. Normalmente viene chiamato il metodo Dispose() che rimuove il collegamento del finalizzatore e trasforma l'oggetto in un oggetto gestito regolare che il garbage collector può rimuovere facilmente.

Ecco un esempio da MSDN di una classe che dispone di risorse gestite e non gestite per la pulizia.

Si noti che le risorse gestite vengono pulite solo se disposing è true, ma le risorse non gestite vengono sempre ripulite.

public class MyResource: IDisposable 
{ 
    // Pointer to an external unmanaged resource. 
    private IntPtr handle; 
    // Other managed resource this class uses. 
    private Component component = new Component(); 
    // Track whether Dispose has been called. 
    private bool disposed = false; 

    // The class constructor. 
    public MyResource(IntPtr handle) 
    { 
     this.handle = handle; 
    } 

    // Implement IDisposable. 
    // Do not make this method virtual. 
    // A derived class should not be able to override this method. 
    public void Dispose() 
    { 
     Dispose(true); 
     // This object will be cleaned up by the Dispose method. 
     // Therefore, you should call GC.SupressFinalize to 
     // take this object off the finalization queue 
     // and prevent finalization code for this object 
     // from executing a second time. 
     GC.SuppressFinalize(this); 
    } 

    // Dispose(bool disposing) executes in two distinct scenarios. 
    // If disposing equals true, the method has been called directly 
    // or indirectly by a user's code. Managed and unmanaged resources 
    // can be disposed. 
    // If disposing equals false, the method has been called by the 
    // runtime from inside the finalizer and you should not reference 
    // other objects. Only unmanaged resources can be disposed. 
    private void Dispose(bool disposing) 
    { 
     // Check to see if Dispose has already been called. 
     if(!this.disposed) 
     { 
      // If disposing equals true, dispose all managed 
      // and unmanaged resources. 
      if(disposing) 
      { 
       // Dispose managed resources. 
       component.Dispose(); 
      } 

      // Call the appropriate methods to clean up 
      // unmanaged resources here. 
      // If disposing is false, 
      // only the following code is executed. 
      CloseHandle(handle); 
      handle = IntPtr.Zero; 

      // Note disposing has been done. 
      disposed = true; 

     } 
    } 

    // Use interop to call the method necessary 
    // to clean up the unmanaged resource. 
    [System.Runtime.InteropServices.DllImport("Kernel32")] 
    private extern static Boolean CloseHandle(IntPtr handle); 

    // Use C# destructor syntax for finalization code. 
    // This destructor will run only if the Dispose method 
    // does not get called. 
    // It gives your base class the opportunity to finalize. 
    // Do not provide destructors in types derived from this class. 
    ~MyResource() 
    { 
     // Do not re-create Dispose clean-up code here. 
     // Calling Dispose(false) is optimal in terms of 
     // readability and maintainability. 
     Dispose(false); 
    } 
} 
+16

Ma osservate che se non avete risorse non gestite allora 'Dispose (false)' non ha esattamente nulla da fare - quindi non avete bisogno di un finalizzatore o di un 'Dispose (bool)'. Sento che il modello standard è eccessivamente complicato per soddisfare casi d'uso che non si verificano quasi mai (e quando lo fanno sono probabilmente una cattiva idea). Ecco uno Preferisco di gran lunga: http://nitoprograms.blogspot.com/2009/08/how-to-implement-idisposable-and.html –

+0

@romkyns "L'uso primario di [IDisposable] è quello di liberare risorse non gestite." (http://msdn.microsoft.com/en-us/library/System.IDisposable.aspx) Quindi non sorprende che il modo standard di implementare IDisposable sia più del necessario se non si dispone di risorse non gestite. Non sono sicuro di cosa intenda per "casi d'uso che non si verificano quasi mai" - avere un mix di risorse gestite e non gestite non è un caso d'uso oscuro. –

+0

Detto questo, sono d'accordo che se il finalizzatore non sta facendo nulla (perché non hai risorse non gestite), allora non ne hai bisogno. Come la maggior parte degli schemi (come quasi tutto, suppongo), ha senso solo usarlo dove ha senso. ;) –

8

Non ci sono distruttori in C#. Questo è un Finalizer, che è una cosa diversa.

La distinzione è se è necessario pulire gli oggetti gestiti o meno. Non vuoi provare a ripulirli nel finalizzatore, in quanto potrebbero essere stati finalizzati.


Sono recentemente capitato di guardare la pagina Destructors della Guida per programmatori C#. Mostra che mi sono sbagliato nella mia risposta, sopra. In particolare, c'è una differenza tra il distruttore e finalizzatore:

class Car 
{ 
    ~Car() // destructor 
    { 
     // cleanup statements... 
    } 
} 

equivale a

protected override void Finalize() 
{ 
    try 
    { 
     // Cleanup statements... 
    } 
    finally 
    { 
     base.Finalize(); 
    } 
} 
+0

quindi perché non solo omettere la chiamata tutti insieme? – ryeguy

+0

Perché si può ancora avere risorse non gestite per pulire ... – Chris

+0

Ma lo smaltimento è fatto all'interno del ciclo If, che non verrà eseguito quando il parametro passato è False (come dal finalizzatore) – ryeguy

16

"L'idea qui è che Dispose (Boolean) sa se si chiamata a fare pulizia esplicita (il booleano è vero) contro l' chiamato a causa di una garbage collection (il booleano è falso). Questa distinzione è utile perché, quando essendo disposti in modo esplicito, il metodo Dispose (Boolean) può tranquillamente eseguire codice utilizzando il tipo di riferimento campi che fanno riferimento ad altri oggetti sapendo per certo che questi altri oggetti non sono stati finalizzati o smaltiti ancora. Quando il booleano è false, il Dispose (Boolean) Metodo non dovrebbe eseguire codice che si riferiscono a campi di tipo di riferimento perché quelle oggetti potrebbe essere già stato finalizzato ".

C'è molto di più informazioni nel “Dispose, Finalization, and Resource Management Design Guidelines” .

Edit:. collegamento

+1

Rispondo allo stesso modo che ho fatto a l'altro ragazzo: perché chiamarlo del tutto dal finalizzatore? – ryeguy

+0

@ryeguy perché in realtà non dovresti implementare il finalizzatore da solo, a meno che non sia davvero necessario. –

+0

Il tuo link "molte più informazioni" è fantastico! –

1

All'interno del caso (smaltimento) che si suppone di chiamare smaltire/chiudere su oggetti gestiti che dispongono di risorse non gestite (ad esempio le connessioni al database) .Quando il finalizzatore è chiamato questi ob i ject non sono più raggiungibili così gli oggetti stessi possono essere finalizzati e non è necessario chiamare su di essi. Anche l'ordine di finalizzazione è indeterminato, quindi è possibile che si chiami dispose su oggetti già disposti.

3

Penso che la confusione sia dovuta al fatto che nel tuo esempio non stai pubblicando alcuna risorsa non gestita. Questi devono anche essere rilasciati quando dispose viene chiamato tramite garbage collection e verrebbero rilasciati all'esterno dello il controllo per disposing. Vedere l'esempio MSDN relativo a releasing unmanaged resources. L'altro che dovrebbe/dovrebbe accadere al di fuori del controllo è una chiamata a qualsiasi metodo Dispose di classe base.

Dall'articolo citato:

protected override void Dispose(bool disposing) 
    { 
     if (disposing) 
     { 
     // Release managed resources. 
     } 
     // Release unmanaged resources. 
     // Set large fields to null. 
     // Call Dispose on your base class. 
     base.Dispose(disposing); 
    } 
+0

Che dire di '~ Derived() { Dispose (false); } 'per derivato? –