Uso corretto dell'interfaccia IDisposable


1659

Dalla lettura della documentazione di Microsoft so che l'uso "primario" IDisposabledell'interfaccia è quello di ripulire le risorse non gestite.

Per me "non gestito" significa cose come connessioni al database, socket, handle di finestre, ecc. Ma ho visto il codice in cui il Dispose()metodo è implementato per liberare risorse gestite , il che mi sembra ridondante, dal momento che il garbage collector dovrebbe occuparsi di quello per te.

Per esempio:

public class MyCollection : IDisposable
{
    private List<String> _theList = new List<String>();
    private Dictionary<String, Point> _theDict = new Dictionary<String, Point>();

    // Die, clear it up! (free unmanaged resources)
    public void Dispose()
    {
        _theList.clear();
        _theDict.clear();
        _theList = null;
        _theDict = null;
    }

La mia domanda è: questo rende la memoria libera del Garbage Collector utilizzata da MyCollectionuna velocità maggiore rispetto al normale?

modifica : finora le persone hanno pubblicato alcuni buoni esempi dell'utilizzo di IDisposable per ripulire risorse non gestite come connessioni al database e bitmap. Ma supponiamo che _theListnel codice sopra riportato contenesse un milione di stringhe e che tu volessi liberare quella memoria ora , piuttosto che aspettare il garbage collector. Il codice sopra riportato lo farebbe?


34
Mi piace la risposta accettata perché ti dice lo "schema" corretto dell'uso di IDisposable, ma come ha detto l'OP nella sua modifica, non risponde alla sua domanda prevista. IDisposable non "chiama" il GC, ma "contrassegna" un oggetto come distruttibile. Ma qual è il vero modo per liberare memoria 'adesso' invece di aspettare che GC inizi? Penso che questa domanda meriti ulteriori discussioni.
Punit Vora,

40
IDisposablenon segna nulla. Il Disposemetodo fa quello che deve fare per ripulire le risorse utilizzate dall'istanza. Questo non ha nulla a che fare con GC.
John Saunders,

4
@John. Capisco IDisposable. Ed è per questo che ho detto che la risposta accettata non risponde alla domanda prevista dall'OP (e modifica di follow-up) sul fatto che IDisposable possa aiutare a <i> liberare memoria </i>. Poiché IDisposablenon ha nulla a che fare con la liberazione della memoria, solo le risorse, quindi come hai detto, non è necessario impostare i riferimenti gestiti su null, cosa che stava facendo OP nel suo esempio. Quindi, la risposta corretta alla sua domanda è "No, non aiuta a liberare memoria più velocemente. In realtà, non aiuta affatto a liberare memoria, solo risorse". Ma comunque, grazie per il tuo contributo.
Punit Vora,

9
@desigeek: in questo caso, non avresti dovuto dire "IDisposable non" chiama "il GC, ma" contrassegna "un oggetto come distruttibile"
John Saunders,

5
@desigeek: non esiste un modo garantito per liberare la memoria in modo deterministico. Potresti chiamare GC.Collect (), ma questa è una richiesta educata, non una richiesta. Tutti i thread in esecuzione devono essere sospesi affinché la Garbage Collection possa continuare - leggere il concetto di safepoints .NET se si desidera saperne di più, ad esempio msdn.microsoft.com/en-us/library/678ysw69(v=vs.110). aspx . Se un thread non può essere sospeso, ad esempio perché c'è una chiamata in codice non gestito, GC.Collect () potrebbe non fare nulla.
Gannet di cemento

Risposte:


2609

Il punto di disporre è liberare risorse non gestite. Deve essere fatto a un certo punto, altrimenti non verranno mai ripuliti. Il garbage collector non sa come chiamare DeleteHandle()una variabile di tipo IntPtr, non sa se deve chiamare o meno DeleteHandle().

Nota : che cos'è una risorsa non gestita ? Se l'hai trovato in Microsoft .NET Framework: è gestito. Se hai cercato MSDN da solo, non è gestito. Qualsiasi cosa tu abbia usato le chiamate P / Invoke per uscire dal bel mondo confortevole di tutto ciò che ti è disponibile in .NET Framework non è gestito - e ora sei responsabile della pulizia.

L'oggetto che hai creato deve esporre un metodo che il mondo esterno può chiamare per ripulire le risorse non gestite. Il metodo può essere nominato come preferisci:

public void Cleanup()

o

public void Shutdown()

Ma invece esiste un nome standardizzato per questo metodo:

public void Dispose()

È stata persino creata un'interfaccia IDisposable, che ha solo un metodo:

public interface IDisposable
{
   void Dispose()
}

Quindi fai in modo che il tuo oggetto esponga l' IDisposableinterfaccia e in questo modo prometti di aver scritto quel singolo metodo per ripulire le risorse non gestite:

public void Dispose()
{
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);
}

E hai finito. Solo che puoi fare di meglio.


Cosa succede se il tuo oggetto ha allocato un System.Drawing.Bitmap da 250 MB (ovvero la classe Bitmap gestita .NET) come una sorta di frame buffer? Certo, questo è un oggetto .NET gestito e il garbage collector lo libererà. Ma vuoi davvero lasciare 250 MB di memoria semplicemente seduto lì - in attesa che il garbage collector alla fine arrivi e lo liberi? Cosa succede se è presente una connessione al database aperta ? Sicuramente non vogliamo che quella connessione rimanga aperta, in attesa che il GC finalizzi l'oggetto.

Se l'utente ha chiamato Dispose()(nel senso che non ha più intenzione di utilizzare l'oggetto) perché non liberarsi di quelle bitmap e connessioni al database dispendiose?

Quindi ora faremo:

  • sbarazzarsi di risorse non gestite (perché dobbiamo), e
  • sbarazzarsi delle risorse gestite (perché vogliamo essere utili)

Quindi aggiorniamo il nostro Dispose()metodo per sbarazzarci di quegli oggetti gestiti:

public void Dispose()
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);

   //Free managed resources too
   if (this.databaseConnection != null)
   {
      this.databaseConnection.Dispose();
      this.databaseConnection = null;
   }
   if (this.frameBufferImage != null)
   {
      this.frameBufferImage.Dispose();
      this.frameBufferImage = null;
   }
}

E tutto va bene, tranne che puoi fare di meglio !


Cosa succede se la persona ha dimenticato di chiamare Dispose()il tuo oggetto? Quindi avrebbero perso alcune risorse non gestite !

Nota: non perderanno risorse gestite , perché alla fine il Garbage Collector verrà eseguito, su un thread in background, e libererà la memoria associata a qualsiasi oggetto non utilizzato. Ciò comprende l'oggetto, ed eventuali oggetti gestiti che si utilizzano (per esempio il Bitmape il DbConnection).

Se la persona ha dimenticato di chiamare Dispose(), possiamo ancora salvare la loro pancetta! Abbiamo ancora un modo per chiamarlo per loro: quando il garbage collector finalmente riesce a liberare (cioè finalizzare) il nostro oggetto.

Nota: il Garbage Collector alla fine libererà tutti gli oggetti gestiti. Quando lo fa, chiama il Finalize metodo sull'oggetto. Il GC non è a conoscenza o si preoccupa del metodo di smaltimento . Era solo un nome che abbiamo scelto per un metodo che chiamiamo quando vogliamo sbarazzarci di cose non gestite.

La distruzione del nostro oggetto da parte del Garbage Collector è il momento perfetto per liberare quelle fastidiose risorse non gestite. Lo facciamo sostituendo il Finalize()metodo.

Nota: in C #, non si sostituisce esplicitamente il Finalize()metodo. Scrivi un metodo che assomiglia a un distruttore C ++ e il compilatore lo considera l'implementazione del Finalize()metodo:

~MyObject()
{
    //we're being finalized (i.e. destroyed), call Dispose in case the user forgot to
    Dispose(); //<--Warning: subtle bug! Keep reading!
}

Ma c'è un bug in quel codice. Vedete, il Garbage Collector viene eseguito su un thread in background ; non conosci l'ordine in cui due oggetti vengono distrutti. È del tutto possibile che nel tuo Dispose()codice, l' oggetto gestito che stai cercando di eliminare (perché volevi essere utile) non sia più presente:

public void Dispose()
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.gdiCursorBitmapStreamFileHandle);

   //Free managed resources too
   if (this.databaseConnection != null)
   {
      this.databaseConnection.Dispose(); //<-- crash, GC already destroyed it
      this.databaseConnection = null;
   }
   if (this.frameBufferImage != null)
   {
      this.frameBufferImage.Dispose(); //<-- crash, GC already destroyed it
      this.frameBufferImage = null;
   }
}

Quindi ciò di cui hai bisogno è un modo per Finalize()dire Dispose()che non dovrebbe toccare alcuna risorsa gestita (perché potrebbero non esserci più), pur liberando risorse non gestite.

Lo schema standard per fare questo è avere Finalize()ed Dispose()entrambi chiamare un terzo metodo (!); dove passi un booleano dicendo se lo stai chiamando Dispose()(invece di Finalize()), il che significa che è sicuro liberare risorse gestite.

A questo metodo interno potrebbe essere assegnato un nome arbitrario come "CoreDispose" o "MyInternalDispose", ma è tradizione chiamarlo Dispose(Boolean):

protected void Dispose(Boolean disposing)

Ma un nome di parametro più utile potrebbe essere:

protected void Dispose(Boolean itIsSafeToAlsoFreeManagedObjects)
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);

   //Free managed resources too, but only if I'm being called from Dispose
   //(If I'm being called from Finalize then the objects might not exist
   //anymore
   if (itIsSafeToAlsoFreeManagedObjects)  
   {    
      if (this.databaseConnection != null)
      {
         this.databaseConnection.Dispose();
         this.databaseConnection = null;
      }
      if (this.frameBufferImage != null)
      {
         this.frameBufferImage.Dispose();
         this.frameBufferImage = null;
      }
   }
}

E cambi l'implementazione del IDisposable.Dispose()metodo in:

public void Dispose()
{
   Dispose(true); //I am calling you from Dispose, it's safe
}

e il tuo finalizzatore per:

~MyObject()
{
   Dispose(false); //I am *not* calling you from Dispose, it's *not* safe
}

Nota : se il tuo oggetto discende da un oggetto che implementa Dispose, non dimenticare di chiamare il metodo Dispose di base quando esegui l'override di Dispose:

public override void Dispose()
{
    try
    {
        Dispose(true); //true: safe to free managed resources
    }
    finally
    {
        base.Dispose();
    }
}

E tutto va bene, tranne che puoi fare di meglio !


Se l'utente chiama Dispose()il tuo oggetto, allora tutto è stato ripulito. Più tardi, quando arriva il garbage collector e chiama Finalize, chiamerà di Disposenuovo.

Questo non è solo uno spreco, ma se il tuo oggetto ha riferimenti spazzatura a oggetti che hai già eliminato dall'ultima chiamata Dispose(), proverai a smaltirli di nuovo!

Noterai nel mio codice che sono stato attento a rimuovere i riferimenti agli oggetti che ho eliminato, quindi non provo a chiamare Disposeun riferimento a oggetti spazzatura. Ma ciò non ha impedito a un insetto sottile di insinuarsi.

Quando l'utente chiama Dispose(): l'handle CursorFileBitmapIconServiceHandle viene distrutto. Più tardi, quando il garbage collector viene eseguito, tenterà di distruggere di nuovo lo stesso handle.

protected void Dispose(Boolean iAmBeingCalledFromDisposeAndNotFinalize)
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle); //<--double destroy 
   ...
}

Il modo in cui lo risolvi è dire al garbage collector che non ha bisogno di preoccuparsi di finalizzare l'oggetto: le sue risorse sono già state ripulite e non è necessario altro lavoro. Puoi farlo chiamando GC.SuppressFinalize()il Dispose()metodo:

public void Dispose()
{
   Dispose(true); //I am calling you from Dispose, it's safe
   GC.SuppressFinalize(this); //Hey, GC: don't bother calling finalize later
}

Ora che l'utente ha chiamato Dispose(), abbiamo:

  • risorse non gestite liberate
  • risorse gestite liberate

Nel GC non ha senso eseguire il finalizzatore: tutto è curato.

Non potrei usare Finalize per ripulire le risorse non gestite?

La documentazione per Object.Finalizedice:

Il metodo Finalize viene utilizzato per eseguire operazioni di pulizia su risorse non gestite trattenute dall'oggetto corrente prima che l'oggetto venga distrutto.

Ma la documentazione MSDN dice anche, per IDisposable.Dispose:

Esegue attività definite dall'applicazione associate alla liberazione, al rilascio o al ripristino di risorse non gestite.

Quindi che cos'è? Qual è il posto per ripulire le risorse non gestite? La risposta è:

È la vostra scelta! Ma scegli Dispose.

Certamente potresti mettere la tua pulizia non gestita nel finalizzatore:

~MyObject()
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);

   //A C# destructor automatically calls the destructor of its base class.
}

Il problema è che non hai idea di quando il garbage collector riuscirà a finalizzare il tuo oggetto. I suoi non-gestita, non necessari, un-utilizzato risorse native saranno restare fino a quando il garbage collector alla fine viene eseguito. Quindi chiamerà il tuo metodo finalizzatore; ripulire le risorse non gestite. La documentazione di Object.Finalize sottolinea questo:

L'ora esatta in cui viene eseguito il finalizzatore non è definita. Per garantire il rilascio deterministico delle risorse per le istanze della classe, implementare un metodo Close o fornire IDisposable.Disposeun'implementazione.

Questa è la virtù dell'utilizzo Disposeper ripulire le risorse non gestite; puoi conoscere e controllare quando le risorse non gestite vengono ripulite. La loro distruzione è "deterministica" .


Per rispondere alla domanda originale: perché non rilasciare la memoria ora, piuttosto che quando GC decide di farlo? Ho un software di riconoscimento facciale che necessità di sbarazzarsi di 530 MB di immagini interne ora , dal momento che non sono più necessari. Quando non lo facciamo: la macchina si arresta in modo irreversibile.

Lettura bonus

Per chiunque ami lo stile di questa risposta (spiegando il perché , quindi il come diventa ovvio), ti suggerisco di leggere il capitolo 1 della COM essenziale di Don Box:

In 35 pagine spiega i problemi dell'uso di oggetti binari e inventa COM davanti ai tuoi occhi. Una volta compreso il perché di COM, le restanti 300 pagine sono ovvie e descrivono in dettaglio l'implementazione di Microsoft.

Penso che ogni programmatore che abbia mai avuto a che fare con oggetti o COM dovrebbe, per lo meno, leggere il primo capitolo. È la migliore spiegazione di qualsiasi cosa in assoluto.

Lettura bonus extra

Quando tutto quello che sai è sbagliato di Eric Lippert

È quindi molto difficile scrivere un finalizzatore corretto e il miglior consiglio che posso darti è di non provarci .


12
Puoi fare di meglio - devi aggiungere una chiamata a GC.SuppressFinalize () in Dispose.
plinto

55
@Daniel Earwicker: è vero. Microsoft vorrebbe smettere del tutto di utilizzare Win32 e attenersi a chiamate .NET Framework piacevolmente estraibili, portatili e indipendenti dal dispositivo. Se vuoi andare in giro per il sistema operativo sottostante; perché pensi di sapere quale sistema operativo è in esecuzione: ti stai prendendo la vita nelle mani. Non tutte le app .NET sono in esecuzione su Windows o su un desktop.
Ian Boyd,

34
Questa è un'ottima risposta, ma penso che trarrebbe comunque beneficio da un elenco di codici finali per un caso standard e per un caso in cui la classe deriva da una baseclass che implementa già Dispose. ad esempio dopo aver letto qui ( msdn.microsoft.com/en-us/library/aa720161%28v=vs.71%29.aspx ) e mi sono confuso su cosa dovrei fare quando derivano dalla classe che implementa già Dispose ( ehi, sono nuovo a questo).
integra753,

5
@GregS e altri: in genere non mi preoccuperei di impostare i riferimenti null. Prima di tutto, significa che non puoi farli readonly, e in secondo luogo, devi fare !=nullcontrolli molto brutti (come nel codice di esempio). Potresti avere una bandiera disposed, ma è più facile non preoccuparsene. .NET GC è abbastanza aggressivo da non considerare più un riferimento a un campo x"usato" quando passa la x.Dispose()linea.
Porges,

7
Nella seconda pagina del libro di Don Box che hai citato, usa l'esempio di un'implementazione O (1) dell'algoritmo di ricerca, i cui "dettagli sono lasciati come esercizio per il lettore". Ho riso.
pulire il

65

IDisposableviene spesso utilizzato per sfruttare l' usingaffermazione e sfruttare un modo semplice per eseguire la pulizia deterministica degli oggetti gestiti.

public class LoggingContext : IDisposable {
    public Finicky(string name) {
        Log.Write("Entering Log Context {0}", name);
        Log.Indent();
    }
    public void Dispose() {
        Log.Outdent();
    }

    public static void Main() {
        Log.Write("Some initial stuff.");
        try {
            using(new LoggingContext()) {
                Log.Write("Some stuff inside the context.");
                throw new Exception();
            }
        } catch {
            Log.Write("Man, that was a heavy exception caught from inside a child logging context!");
        } finally {
            Log.Write("Some final stuff.");
        }
    }
}

6
Mi piace, personalmente, ma non si adatta alle linee guida per la progettazione del framework.
mqp,

4
Lo considererei una progettazione corretta perché consente di ottenere ambiti deterministici e semplici costruzioni / pulizie dell'ambito, in particolare se mescolati a blocchi di gestione delle eccezioni, blocco e utilizzo di risorse non gestite in modi complessi. La lingua offre questo come una funzionalità di prima classe.
yfeldblum,

Non segue esattamente le regole specificate nell'FDG ma è certamente un uso valido del modello in quanto è richiesto per essere utilizzato dall'istruzione "using".
Scott Dorman,

2
Finché Log.Outdent non getta, non c'è assolutamente nulla di sbagliato in questo.
Daniel Earwicker,

1
Le varie risposte a È abusivo usare IDisposable e "usare" come mezzo per ottenere "comportamenti mirati" per la sicurezza delle eccezioni? andare un po 'più in dettaglio sul perché a persone diverse piace / non piace questa tecnica. È alquanto controverso.
Brian,

44

Lo scopo del modello Dispose è di fornire un meccanismo per ripulire le risorse gestite e non gestite e quando ciò si verifica dipende da come viene chiamato il metodo Dispose. Nel tuo esempio, l'uso di Dispose in realtà non sta facendo nulla di simile allo smaltimento, poiché la cancellazione di un elenco non ha alcun impatto sulla raccolta che viene eliminata. Allo stesso modo, anche le chiamate per impostare le variabili su null non hanno alcun impatto sul GC.

Puoi dare un'occhiata a questo articolo per maggiori dettagli su come implementare il modello Dispose, ma in pratica è simile al seguente:

public class SimpleCleanup : IDisposable
{
    // some fields that require cleanup
    private SafeHandle handle;
    private bool disposed = false; // to detect redundant calls

    public SimpleCleanup()
    {
        this.handle = /*...*/;
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!disposed)
        {
            if (disposing)
            {
                // Dispose managed resources.
                if (handle != null)
                {
                    handle.Dispose();
                }
            }

            // Dispose unmanaged managed resources.

            disposed = true;
        }
    }

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

Il metodo che è il più importante qui è il Dispose (bool), che in realtà funziona in due circostanze diverse:

  • disposing == true: il metodo è stato chiamato direttamente o indirettamente dal codice di un utente. Le risorse gestite e non gestite possono essere eliminate.
  • disposing == false: il metodo è stato chiamato dal runtime dall'interno del finalizzatore e non è necessario fare riferimento ad altri oggetti. È possibile eliminare solo le risorse non gestite.

Il problema di lasciare semplicemente che il GC si occupi della pulizia è che non hai alcun controllo reale su quando il GC eseguirà un ciclo di raccolta (puoi chiamare GC.Collect (), ma in realtà non dovresti) quindi le risorse potrebbero rimanere in giro più a lungo del necessario. Ricordare che chiamare Dispose () in realtà non provoca un ciclo di raccolta o in alcun modo indurre il GC a raccogliere / liberare l'oggetto; fornisce semplicemente i mezzi per pulire in modo più deterministico le risorse utilizzate e dire al GC che questa pulizia è già stata eseguita.

L'intero punto di IDisposable e il modello dispose non riguardano la liberazione immediata della memoria. L'unica volta in cui una chiamata a Dispose avrà addirittura la possibilità di liberare immediatamente la memoria è quando gestisce lo scenario disposing == false e manipola le risorse non gestite. Per il codice gestito, la memoria non verrà effettivamente recuperata fino a quando il GC non eseguirà un ciclo di raccolta, sul quale non si ha alcun controllo (a parte chiamare GC.Collect (), che ho già menzionato non è una buona idea).

Il tuo scenario non è realmente valido poiché le stringhe in .NET non usano risorse non allineate e non implementano IDisposable, non c'è modo di forzare la loro "pulizia".


4
Non hai dimenticato di implementare il finalizzatore?
Budda,

@Budda: No, sta usando un SafeHandle. Non c'è bisogno di un distruttore.
Henk Holterman,

9
+1 per l'aggiunta della rete di sicurezza per più chiamate a Dispose (). Le specifiche indicano che più chiamate dovrebbero essere sicure. Troppe classi Microsoft non riescono a implementarlo e ottieni la fastidiosa ObjectDisposedException.
Jesse Chisholm,

5
Ma Dispose (bool disposing) è il tuo metodo sulla tua classe SimpleCleanup e non verrà mai chiamato dal framework. Dato che lo si chiama solo con "true" come parametro, "disposing" non sarà mai falso. Il tuo codice è molto simile all'esempio MSDN per IDisposable, ma manca il finalizzatore, come ha sottolineato @Budda, che è da dove verrebbe la chiamata con disposing = false.
yoyo

19

Non ci dovrebbero essere ulteriori chiamate ai metodi di un oggetto dopo che è stato chiamato Dispose su di esso (anche se un oggetto dovrebbe tollerare ulteriori chiamate a Dispose). Pertanto l'esempio nella domanda è sciocco. Se viene chiamato Dispose, l'oggetto stesso può essere scartato. Quindi l'utente dovrebbe semplicemente scartare tutti i riferimenti all'intero oggetto (impostarli su null) e tutti gli oggetti correlati interni ad esso verranno automaticamente ripuliti.

Per quanto riguarda la domanda generale su gestito / non gestito e la discussione in altre risposte, penso che qualsiasi risposta a questa domanda debba iniziare con una definizione di risorsa non gestita.

Ciò che si riduce è che c'è una funzione che puoi chiamare per mettere il sistema in uno stato, e c'è un'altra funzione che puoi chiamare per riportarlo fuori da quello stato. Ora, nell'esempio tipico, il primo potrebbe essere una funzione che restituisce un handle di file e il secondo potrebbe essere una chiamata a CloseHandle.

Ma - e questa è la chiave - potrebbero essere tutte le coppie di funzioni corrispondenti. Uno costruisce uno stato, l'altro lo abbatte. Se lo stato è stato creato ma non ancora demolito, esiste un'istanza della risorsa. Devi fare in modo che lo smontaggio avvenga al momento giusto - la risorsa non è gestita dal CLR. L'unico tipo di risorsa gestito automaticamente è la memoria. Ne esistono di due tipi: il GC e lo stack. I tipi di valore sono gestiti dallo stack (o facendo l'autostop all'interno dei tipi di riferimento), mentre i tipi di riferimento sono gestiti dal GC.

Queste funzioni possono causare cambiamenti di stato che possono essere liberamente interlacciati o potrebbe essere necessario annidarli perfettamente. Le modifiche allo stato potrebbero essere thread-safe o potrebbero non esserlo.

Guarda l'esempio nella domanda di giustizia. Le modifiche al rientro del file di registro devono essere perfettamente nidificate oppure tutto va storto. Inoltre, è improbabile che siano sicuri per il thread.

È possibile fare l'autostop con il Garbage Collector per ripulire le risorse non gestite. Ma solo se le funzioni di cambio di stato sono thread-safe e due stati possono avere vite che si sovrappongono in alcun modo. Quindi l'esempio di giustizia di una risorsa NON deve avere un finalizzatore! Semplicemente non aiuterebbe nessuno.

Per questo tipo di risorse, puoi semplicemente implementare IDisposable, senza finalizzatore. Il finalizzatore è assolutamente facoltativo, deve esserlo. Questo è sorpreso o nemmeno menzionato in molti libri.

È quindi necessario utilizzare l' usingistruzione per avere la possibilità di assicurarsi che Disposevenga chiamato. Questo è essenzialmente come fare l'autostop con lo stack (così come il finalizzatore sta nel GC, usingè nello stack).

La parte mancante è che devi scrivere manualmente Dispose e farlo chiamare sui tuoi campi e sulla tua classe base. I programmatori C ++ / CLI non devono farlo. Il compilatore lo scrive per loro nella maggior parte dei casi.

Esiste un'alternativa, che preferisco per gli stati che nidificano perfettamente e non sono sicuri (a parte qualsiasi altra cosa, evitare IDisposable ti risparmia il problema di avere una discussione con qualcuno che non può resistere all'aggiunta di un finalizzatore per ogni classe che implementa IDisposable) .

Invece di scrivere una classe, scrivi una funzione. La funzione accetta un delegato di richiamare a:

public static void Indented(this Log log, Action action)
{
    log.Indent();
    try
    {
        action();
    }
    finally
    {
        log.Outdent();
    }
}

E quindi un semplice esempio sarebbe:

Log.Write("Message at the top");
Log.Indented(() =>
{
    Log.Write("And this is indented");

    Log.Indented(() =>
    {
        Log.Write("This is even more indented");
    });
});
Log.Write("Back at the outermost level again");

Il lambda che viene passato funge da blocco di codice, quindi è come se la tua struttura di controllo fosse utilizzata per lo stesso scopo using, tranne per il fatto che non hai più il pericolo che il chiamante l'abusasse. Non è possibile che non riescano a ripulire la risorsa.

Questa tecnica è meno utile se la risorsa è del tipo che potrebbe avere vite sovrapposte, perché quindi si desidera poter costruire la risorsa A, quindi la risorsa B, quindi uccidere la risorsa A e successivamente uccidere la risorsa B. Non è possibile farlo se hai costretto l'utente a nidificare perfettamente in questo modo. Ma poi è necessario utilizzare IDisposable(ma ancora senza finalizzatore, a meno che non sia stata implementata la sicurezza thread, che non è gratuita).


ri: "Non ci dovrebbero essere ulteriori chiamate ai metodi di un oggetto dopo che è stato chiamato Dispose su di esso". "Dovrebbe" essere la parola chiave. Se sono in corso azioni asincrone, queste potrebbero entrare dopo che l'oggetto è stato eliminato. Causare un ObjectDisposedException.
Jesse Chisholm,

La tua sembra essere l'unica risposta diversa dalla mia che tocca l'idea che le risorse non gestite incapsulano lo stato che il GC non capisce. Un aspetto chiave di una risorsa non gestita, tuttavia, è che una o più entità il cui stato potrebbe necessitare di ripulire il proprio stato possono continuare a esistere anche se l'oggetto che "possiede" la risorsa non lo è. Ti piace la mia definizione? Abbastanza simile, ma penso che renda la "risorsa" un po 'più simile al nome (è "l'accordo" dell'oggetto esterno di modificarne il comportamento, in cambio della notifica di quando i suoi servizi non sono più necessari)
supercat

@supercat - se sei interessato, ho scritto il seguente post un paio di giorni dopo aver scritto la risposta di cui sopra: smellegantcode.wordpress.com/2009/02/13/…
Daniel Earwicker,

1
@DanielEarwicker: articolo interessante, anche se riesco a pensare ad almeno un tipo di risorsa non gestita che non copri davvero: abbonamenti ad eventi da oggetti di lunga durata. Gli abbonamenti agli eventi sono fungibili, ma anche se la memoria fosse illimitata, il loro smaltimento potrebbe essere costoso. Ad esempio, un enumeratore per una raccolta che consente la modifica durante l'enumerazione potrebbe dover abbonarsi per aggiornare le notifiche dalla raccolta e una raccolta potrebbe essere aggiornata più volte nel corso della sua vita. Se gli enumeratori vengono abbandonati senza annullare l'iscrizione ...
supercat

1
La coppia di operazioni entered exitè il nucleo di come penso a una risorsa. Iscriversi / annullare l'iscrizione agli eventi dovrebbe adattarsi a questo senza difficoltà. In termini di caratteristiche ortogonali / fungibili è praticamente indistinguibile da una perdita di memoria. (Ciò non sorprende poiché l'abbonamento sta solo aggiungendo oggetti a un elenco.)
Daniel Earwicker,

17

Scenari che utilizzo IDisposable: ripulire le risorse non gestite, annullare l'iscrizione agli eventi, chiudere le connessioni

Il linguaggio che uso per implementare IDisposable ( non thread - safe ):

class MyClass : IDisposable {
    // ...

    #region IDisposable Members and Helpers
    private bool disposed = false;

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

    private void Dispose(bool disposing) {
        if (!this.disposed) {
            if (disposing) {
                // cleanup code goes here
            }
            disposed = true;
        }
    }

    ~MyClass() {
        Dispose(false);
    }
    #endregion
}

Modello spiegazione completa è disponibile all'indirizzo msdn.microsoft.com/en-us/library/b1yfkh5e.aspx
LicenseQ

3
non dovrebbe mai includere un finalizzatore a meno che tu non abbia risorse non gestite. Anche in questo caso, l'implementazione preferita è il wrapping della risorsa non gestita in SafeHandle.
Dave Black,

11

Sì, quel codice è completamente ridondante e superfluo e non fa fare al garbage collector qualsiasi cosa altrimenti non farebbe (una volta che un'istanza di MyCollection esce dall'ambito, vale a dire.) Soprattutto le .Clear()chiamate.

Rispondi alla tua modifica: sorta di. Se lo faccio:

public void WasteMemory()
{
    var instance = new MyCollection(); // this one has no Dispose() method
    instance.FillItWithAMillionStrings();
}

// 1 million strings are in memory, but marked for reclamation by the GC

È funzionalmente identico a questo ai fini della gestione della memoria:

public void WasteMemory()
{
    var instance = new MyCollection(); // this one has your Dispose()
    instance.FillItWithAMillionStrings();
    instance.Dispose();
}

// 1 million strings are in memory, but marked for reclamation by the GC

Se hai davvero davvero bisogno di liberare la memoria in questo istante, chiama GC.Collect(). Non c'è motivo di farlo qui, però. La memoria verrà liberata quando è necessario.


2
ri: "La memoria verrà liberata quando è necessario." Piuttosto, "quando GC decide che è necessario". Potresti riscontrare problemi di prestazioni del sistema prima che GC decida che la memoria è davvero necessaria. Liberarlo ora potrebbe non essere essenziale, ma può essere utile.
Jesse Chisholm,

1
Esistono alcuni casi angolari in cui l'annullamento dei riferimenti all'interno di una raccolta può accelerare la raccolta dei rifiuti degli articoli a cui si fa riferimento. Ad esempio, se un array di grandi dimensioni viene creato e riempito con riferimenti a elementi di nuova creazione più piccoli, ma non è necessario per molto tempo, l'abbandono dell'array può causare il mantenimento di tali elementi fino al GC di livello 2 successivo, mentre si azzera prima, gli oggetti possono essere idonei per il GC di livello 0 o livello successivo. A dire il vero, avere grandi oggetti di breve durata sul
mucchio di

1
... azzerando tali array prima di abbandonarli, a volte riduco l'impatto del GC.
supercat

11

Se MyCollection verrà comunque raccolta dei rifiuti, non dovresti averne bisogno. In questo modo si limiterà a sfornare la CPU più del necessario e potrebbe anche invalidare alcune analisi precalcolate che il Garbage Collector ha già eseguito.

Io uso IDisposable cose come assicurarsi che i thread siano disposti correttamente, insieme a risorse non gestite.

MODIFICA In risposta al commento di Scott:

L'unica volta che le metriche sulle prestazioni del GC sono influenzate è quando viene effettuata una chiamata [sic] GC.Collect () "

Concettualmente, il GC mantiene una vista del grafico di riferimento dell'oggetto e di tutti i riferimenti ad esso dai frame stack dei thread. Questo heap può essere piuttosto grande e comprendere molte pagine di memoria. Come ottimizzazione, il GC memorizza nella cache l'analisi delle pagine che è improbabile che cambi molto spesso per evitare di ripetere la scansione inutilmente della pagina. Il GC riceve una notifica dal kernel quando i dati in una pagina cambiano, quindi sa che la pagina è sporca e richiede una nuova scansione. Se la raccolta è in Gen0, è probabile che anche altre cose nella pagina stiano cambiando, ma questo è meno probabile in Gen1 e Gen2. Aneddoticamente, questi hook non erano disponibili in Mac OS X per il team che ha portato il GC su Mac per far funzionare il plug-in Silverlight su quella piattaforma.

Un altro punto contro lo smaltimento superfluo delle risorse: immagina una situazione in cui un processo sta scaricando. Immagina anche che il processo è in corso da un po 'di tempo. È probabile che molte delle pagine di memoria di quel processo siano state scambiate su disco. Per lo meno non sono più nella cache L1 o L2. In una situazione del genere non ha senso che un'applicazione in fase di scarico sostituisca tutti i dati e le tabelle codici in memoria per "liberare" le risorse che verranno rilasciate dal sistema operativo comunque al termine del processo. Questo vale per le risorse gestite e anche determinate non gestite. Solo le risorse che mantengono attivi i thread non in background devono essere eliminate, altrimenti il ​​processo rimarrà attivo.

Ora, durante la normale esecuzione, ci sono risorse effimere che devono essere ripulite correttamente (poiché @fezmonkey indica connessioni al database, socket, handle di finestre ) per evitare perdite di memoria non gestite. Questi sono i tipi di cose che devono essere smaltiti. Se crei una classe che possiede un thread (e di per sé intendo che l'ha creato e quindi è responsabile di assicurarne l'arresto, almeno secondo il mio stile di codifica), allora quella classe molto probabilmente deve implementare IDisposablee abbattere il thread durante Dispose.

Il framework .NET utilizza l' IDisposableinterfaccia come segnale, anche di avvertimento, per gli sviluppatori che questa classe deve essere eliminata. Non riesco a pensare a nessun tipo nel framework che implementa IDisposable(escluse le implementazioni esplicite dell'interfaccia) dove lo smaltimento è facoltativo.


Calling Dispose è perfettamente valido, legale e incoraggiato. Gli oggetti che implementano IDisposable di solito lo fanno per un motivo. L'unica volta che le metriche delle prestazioni del GC sono influenzate è quando viene effettuata una chiamata GC.Collect ().
Scott Dorman,

Per molte classi .net, lo smaltimento è "piuttosto" facoltativo, il che significa che abbandonare le istanze "di solito" non causerà alcun problema finché non si impazzisce nel creare nuove istanze e abbandonarle. Ad esempio, il codice generato dal compilatore per i controlli sembra creare caratteri quando i controlli vengono istanziati e abbandonarli quando i moduli vengono eliminati; se si creano e eliminano migliaia di controlli, questo potrebbe legare migliaia di handle GDI, ma nella maggior parte dei casi i controlli non vengono creati e distrutti così tanto. Tuttavia, si dovrebbe ancora cercare di evitare tale abbandono.
supercat

1
Nel caso dei caratteri, ho il sospetto che il problema sia che Microsoft non ha mai veramente definito quale entità è responsabile della disposizione dell'oggetto "carattere" assegnato a un controllo; in alcuni casi, un controllo può condividere un carattere con un oggetto più longevo, quindi avere il controllo Dispose il carattere sarebbe male. In altri casi, un carattere verrà assegnato a un controllo e da nessun'altra parte, quindi se il controllo non lo dispone, nessuno lo farà. Per inciso, questa difficoltà con i caratteri avrebbe potuto essere evitata se ci fosse stata una classe FontTemplate separata non disponibile, poiché i controlli non sembrano usare l'handle GDI del loro carattere.
supercat

Per quanto riguarda le Dispose()chiamate opzionali , consultare: stackoverflow.com/questions/913228/…
RJ Cuthbertson,

7

Nell'esempio che hai pubblicato, non "libera ancora la memoria". Tutta la memoria viene raccolta in modo inutile, ma può consentire la raccolta in una generazione precedente . Dovresti eseguire alcuni test per essere sicuro.


Le Linee guida per la progettazione del quadro sono linee guida e non regole. Ti dicono a cosa serve principalmente l'interfaccia, quando usarla, come usarla e quando non usarla.

Una volta ho letto il codice che era un semplice RollBack () in caso di errore utilizzando IDisposable. La classe MiniTx in basso controlla un flag su Dispose () e se la Commitchiamata non è mai avvenuta, si richiamerebbe Rollbackda sola. Ha aggiunto un livello di riferimento indiretto che rende il codice chiamante molto più facile da capire e mantenere. Il risultato è simile a:

using( MiniTx tx = new MiniTx() )
{
    // code that might not work.

    tx.Commit();
} 

Ho anche visto il codice di temporizzazione / registrazione fare la stessa cosa. In questo caso il metodo Dispose () ha fermato il timer e registrato che il blocco era uscito.

using( LogTimer log = new LogTimer("MyCategory", "Some message") )
{
    // code to time...
}

Ecco quindi un paio di esempi concreti che non eseguono la pulizia delle risorse non gestita, ma hanno usato IDisposable per creare codice più pulito.


Dai un'occhiata all'esempio di @Daniel Earwicker usando funzioni di ordine superiore. Per benchmarking, tempistica, registrazione ecc. Sembra molto più semplice.
Aluan Haddad,


6

Non ripeterò le solite cose sull'uso o sulla liberazione di risorse non gestite, che sono state tutte coperte. Ma vorrei sottolineare quello che sembra un malinteso comune.
Dato il seguente codice

Classe pubblica LargeStuff
  Implementa IDisposable
  Private _Large as string ()

  'Qualche strano codice che significa _Large ora contiene diversi milioni di stringhe lunghe.

  Public Sub Dispose () Implementa IDisposable.Dispose
    _Large = Nothing
  End Sub

Mi rendo conto che l'implementazione a gettare non segue le linee guida attuali, ma spero che abbiate tutti l'idea.
Ora, quando viene chiamato Dispose, quanta memoria viene liberata?

Risposta: nessuna. Non ha senso aggiungere un finalizzatore per chiamare il metodo Dispose mostrato sopra. Ciò ritarderà semplicemente la rivendicazione della memoria per consentire l'esecuzione del finalizzatore.
La chiamata a Dispose può rilasciare risorse non gestite, NON PUO 'recuperare la memoria gestita, solo il GC può farlo. Ciò non vuol dire che quanto sopra non sia una buona idea, seguire il modello sopra è ancora una buona idea in realtà. Una volta che Dispose è stato eseguito, non c'è nulla che impedisca al GC di rivendicare la memoria utilizzata da _Large, anche se l'istanza di LargeStuff potrebbe essere ancora nell'ambito. Le stringhe in _Large possono anche essere nella gen 0 ma l'istanza di LargeStuff potrebbe essere la gen 2, quindi, di nuovo, la memoria verrebbe rivendicata prima.


1
Se un'istanza di LargeStuffè in circolazione da abbastanza tempo per passare alla Generazione 2 e se _Largecontiene un riferimento a una stringa appena creata che si trova nella Generazione 0, quindi se l'istanza di LargeStuffviene abbandonata senza annullamento _Large, allora la stringa a cui fa riferimento _Largesarà conservato fino alla prossima collezione Gen2. L'azzeramento _Largepuò consentire l'eliminazione della stringa nella prossima raccolta Gen0. Nella maggior parte dei casi, l'annullamento dei riferimenti non è utile, ma ci sono casi in cui può offrire alcuni vantaggi.
supercat

5

Oltre al suo uso primario come modo per controllare la durata delle risorse di sistema (completamente coperto dalla risposta straordinaria di Ian , complimenti!), La combinazione IDisposable / using può anche essere usata per valutare il cambiamento di stato delle risorse globali (critiche) : la console , i thread , il processo , qualsiasi oggetto globale come un'istanza dell'applicazione .

Ho scritto un articolo su questo modello: http://pragmateek.com/c-scope-your-global-state-changes-with-idisposable-and-the-using-statement/

Illustra come proteggere alcuni stati globali spesso utilizzati in modo riutilizzabile e leggibile : colori della console , cultura del thread corrente , proprietà dell'oggetto dell'applicazione Excel ...


4

Semmai, mi aspetto che il codice sia meno efficiente di quando lo tralasciamo.

Chiamare i metodi Clear () non è necessario e il GC probabilmente non lo farebbe se Dispose non lo facesse ...


2

Ci sono cose che l' Dispose()operazione fa nel codice di esempio che potrebbero avere un effetto che non si verificherebbe a causa di un normale GC MyCollectiondell'oggetto.

Se gli oggetti a cui fanno riferimento _theListo a _theDictcui fanno riferimento altri oggetti, quell'oggetto List<>o l' Dictionary<>oggetto non saranno soggetti alla raccolta ma improvvisamente non avranno alcun contenuto. Se non ci fossero operazioni Dispose () come nell'esempio, quelle raccolte conterrebbero comunque il loro contenuto.

Ovviamente, se questa fosse la situazione, la definirei un progetto non funzionante - sto solo sottolineando (pedanticamente, suppongo) che l' Dispose()operazione potrebbe non essere completamente ridondante, a seconda che ci siano altri usi del List<>o Dictionary<>che non lo sono mostrato nel frammento.


Sono campi privati, quindi penso sia giusto presumere che il PO non stia dando riferimenti a loro.
mqp,

1) il frammento di codice è solo un esempio di codice, quindi sto solo sottolineando che potrebbe esserci un effetto collaterale che è facile da trascurare; 2) i campi privati ​​sono spesso il bersaglio di una proprietà / metodo getter - forse troppo (getter / setter sono considerati da alcune persone un po 'anti-pattern).
Michael Burr,

2

Un problema con la maggior parte delle discussioni sulle "risorse non gestite" è che in realtà non definiscono il termine, ma sembrano implicare che abbia qualcosa a che fare con il codice non gestito. Mentre è vero che molti tipi di risorse non gestite si interfacciano con codice non gestito, pensare a risorse non gestite in tali termini non è utile.

Invece, si dovrebbe riconoscere ciò che tutte le risorse gestite hanno in comune: implicano tutti un oggetto che chiede a qualche "cosa" esterna di fare qualcosa per suo conto, a scapito di altre "cose", e l'altra entità che accetta di farlo fino a ulteriore avviso. Se l'oggetto fosse abbandonato e svanisse senza lasciare traccia, nulla direbbe mai a quella "cosa" esterna che non era più necessario modificare il suo comportamento per conto dell'oggetto che non esisteva più; di conseguenza, l'utilità della cosa sarebbe ridotta in modo permanente.

Una risorsa non gestita, quindi, rappresenta un accordo da parte di una "cosa" esterna per alterare il suo comportamento per conto di un oggetto, il che danneggerebbe inutilmente l'utilità di quella "cosa" esterna se l'oggetto fosse abbandonato e cessasse di esistere. Una risorsa gestita è un oggetto che è il beneficiario di tale accordo, ma che si è registrato per ricevere una notifica se viene abbandonato e che utilizzerà tale notifica per mettere in ordine i suoi affari prima che venga distrutto.


Bene, IMO, la definizione di oggetto non gestito è chiara; qualsiasi oggetto non GC .
Eonil,

1
@Eonil: Unmanaged Object! = Risorsa non gestita. Cose come gli eventi possono essere implementate interamente usando oggetti gestiti, ma costituiscono comunque risorse non gestite perché - almeno nel caso di oggetti di breve durata che si abbonano ad eventi di oggetti di lunga durata - il GC non sa nulla su come ripulirli .
supercat,


2

Primo di definizione. Per me risorsa non gestita significa una classe, che implementa l'interfaccia IDisposable o qualcosa creato con l'utilizzo di chiamate a DLL. GC non sa come gestire tali oggetti. Se ad esempio la classe ha solo tipi di valore, non considero questa classe come classe con risorse non gestite. Per il mio codice seguo le prossime pratiche:

  1. Se la classe creata da me utilizza alcune risorse non gestite, significa che dovrei anche implementare l'interfaccia IDisposable per pulire la memoria.
  2. Pulisci gli oggetti non appena ho finito di usarli.
  3. Nel mio metodo di eliminazione eseguo l'iterazione su tutti i membri della classe IDisposable e chiamo Dispose.
  4. Nel mio metodo Dispose chiama GC.SuppressFinalize (questo) per comunicare al garbage collector che il mio oggetto era già stato ripulito. Lo faccio perché chiamare GC è un'operazione costosa.
  5. Come ulteriore precauzione, provo a rendere possibile la chiamata di Dispose () più volte.
  6. Qualche volta aggiungo un membro privato _disposto e il check-in chiama il metodo se l'oggetto è stato ripulito. E se è stato ripulito, quindi genera ObjectDisposedException Il
    modello seguente mostra ciò che ho descritto a parole come esempio di codice:

public class SomeClass : IDisposable
    {
        /// <summary>
        /// As usually I don't care was object disposed or not
        /// </summary>
        public void SomeMethod()
        {
            if (_disposed)
                throw new ObjectDisposedException("SomeClass instance been disposed");
        }

        public void Dispose()
        {
            Dispose(true);
        }

        private bool _disposed;

        protected virtual void Dispose(bool disposing)
        {
            if (_disposed)
                return;
            if (disposing)//we are in the first call
            {
            }
            _disposed = true;
        }
    }

1
"Per me risorsa non gestita significa una classe, che implementa l'interfaccia IDisposable o qualcosa di creato con l'utilizzo di chiamate a DLL." Quindi stai dicendo che qualsiasi tipo che is IDisposabledovrebbe essere considerato una risorsa non gestita? Non sembra corretto. Inoltre, se il tipo di impianto è un tipo di valore puro, sembra suggerire che non debba essere smaltito. Anche questo sembra sbagliato.
Aluan Haddad,

Tutti giudicano da solo. Non mi piace aggiungere al mio codice qualcosa solo per motivi di aggiunta. Significa che se aggiungo IDisposable, significa che ho creato un tipo di funzionalità che GC non è in grado di gestire o suppongo che non sarà in grado di gestire correttamente la sua durata.
Yuriy Zaletskyy,

2

L'esempio di codice fornito non è un buon esempio di IDisposableutilizzo. La cancellazione del dizionario normalmente non dovrebbe andare al Disposemetodo. Gli elementi del dizionario verranno eliminati e eliminati quando non rientrano nell'ambito di applicazione. IDisposablel'implementazione è necessaria per liberare alcuni gestori / memoria che non verranno rilasciati / liberati anche dopo che sono stati esclusi.

L'esempio seguente mostra un buon esempio di pattern IDisposable con alcuni codici e commenti.

public class DisposeExample
{
    // A base class that implements IDisposable. 
    // By implementing IDisposable, you are announcing that 
    // instances of this type allocate scarce resources. 
    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. 
        protected virtual 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);
        }
    }
    public static void Main()
    {
        // Insert code here to create 
        // and use the MyResource object.
    }
}

1

Il caso d'uso più giustificabile per lo smaltimento delle risorse gestite è la preparazione del GC al recupero di risorse che altrimenti non verrebbero mai raccolte.

Un primo esempio sono i riferimenti circolari.

Mentre è buona prassi utilizzare modelli che evitano riferimenti circolari, se si finisce con (ad esempio) un oggetto "figlio" che ha un riferimento al suo "genitore", questo può interrompere la raccolta GC del genitore se si abbandona e basta il riferimento e si basano su GC - inoltre se hai implementato un finalizzatore, non verrà mai chiamato.

L'unico modo per aggirare il problema è interrompere manualmente i riferimenti circolari impostando i riferimenti Parent su null sui figli.

L'implementazione di IDisposable su genitori e figli è il modo migliore per farlo. Quando si chiama Dispose sul padre, chiamare Dispose su tutti i figli e, nel metodo Dispose figlio, impostare i riferimenti padre su null.


4
Per la maggior parte, il GC non funziona identificando oggetti morti, ma piuttosto identificando quelli vivi. Dopo ogni ciclo gc, per ogni oggetto registrato per la finallizzazione, viene memorizzato sull'heap di oggetti di grandi dimensioni o è il bersaglio di un live WeakReference, il sistema controllerà un flag che indica che è stato trovato un riferimento root attivo nell'ultimo ciclo GC e aggiungerà l'oggetto a una coda di oggetti che richiedono una finalizzazione immediata, rilascerà l'oggetto dall'heap di oggetti di grandi dimensioni o invaliderà il riferimento debole. I riferimenti circolari non manterranno vivi gli oggetti se non esistono altri riferimenti.
Supercat,

1

Vedo che molte risposte si sono spostate per parlare dell'utilizzo di IDisposable sia per le risorse gestite che per quelle non gestite. Suggerirei questo articolo come una delle migliori spiegazioni che ho trovato su come IDisposable dovrebbe effettivamente essere usato.

https://www.codeproject.com/Articles/29534/IDisposable-What-Your-Mother-Never-Told-You-About

Per la vera domanda; se si utilizza IDisposable per ripulire gli oggetti gestiti che occupano molta memoria, la risposta breve sarebbe no . Il motivo è che una volta che hai smaltito un IDisposable dovresti lasciarlo fuori dal campo di applicazione. A quel punto anche tutti gli oggetti figlio referenziati non rientrano nell'ambito e verranno raccolti.

L'unica vera eccezione a ciò sarebbe se hai molta memoria legata negli oggetti gestiti e hai bloccato quel thread in attesa del completamento di un'operazione. Se quegli oggetti dove non saranno necessari dopo il completamento della chiamata, l'impostazione di tali riferimenti su null potrebbe consentire al garbage collector di raccoglierli prima. Ma quello scenario rappresenterebbe un codice errato che doveva essere sottoposto a refactoring, non un caso d'uso di IDisposable.


1
Non ho capito perché qualcuno abbia messo -1 alla tua risposta
Sebastian Oscar Lopez,
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.