c# - pattern - vb.net dispose




Uso corretto dell'interfaccia IDisposable (13)

So dalla lettura della documentazione MSDN che l'uso "primario" dell'interfaccia IDisposable serve a ripulire le risorse non gestite.

Per me, "non gestito" significa cose come connessioni di database, socket, handle di finestre, ecc. Ma ho visto il codice in cui il metodo Dispose() è implementato per liberare risorse gestite , che mi sembra ridondante, dal momento che il garbage collector dovrebbe prenditi cura di questo 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 è: fa in modo che la memoria libera usata da MyCollection sia più veloce di quanto normalmente farebbe?

edit : Finora le persone hanno pubblicato alcuni buoni esempi di utilizzo di IDisposable per ripulire risorse non gestite come le connessioni al database e le bitmap. Supponiamo però che l' _theList del codice precedente contenesse un milione di stringhe e che tu volessi liberare quella memoria ora , piuttosto che aspettare il garbage collector. Il codice precedente lo realizzerebbe?


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

Nota : che cos'è una risorsa non gestita ? Se lo hai trovato in Microsoft .NET Framework: è gestito. Se sei andato in giro su MSDN da solo, non è gestito. Qualunque cosa tu abbia usato per chiamare P / Invoke per uscire dal mondo piacevole e comodo di tutto quello che hai a disposizione in .NET Framwork non è gestito - e ora sei responsabile della sua 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()

public void Shutdown()

Ma invece c'è un nome standard per questo metodo:

public void Dispose()

C'era persino un'interfaccia creata, IDisposable , che ha solo quel metodo:

public interface IDisposable
{
   void Dispose()
}

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

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

E hai finito. Tranne che puoi fare di meglio.

Cosa succede se il proprio oggetto ha allocato un System.Drawing.Bitmap 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 solo seduto lì - aspettando che il netturbino finisca per arrivare e liberarlo? Cosa succede se c'è una connessione al database aperta ? Sicuramente non vogliamo che questa connessione sia aperta, in attesa che il GC finalizzi l'oggetto.

Se l'utente ha chiamato Dispose() (il che significa che non pianificano più l'utilizzo dell'oggetto) perché non eliminare quei bitmap e le connessioni di database inutili?

Quindi ora lo faremo:

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

Quindi aggiorna il nostro metodo Dispose() per sbarazzarti 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() sul tuo oggetto? Quindi avrebbero perso alcune risorse non gestite !

Nota: non perderanno le risorse gestite , perché alla fine il garbage collector verrà eseguito su un thread in background e libererà la memoria associata agli oggetti inutilizzati. Ciò includerà il tuo oggetto e tutti gli oggetti gestiti che utilizzi (ad esempio Bitmap e DbConnection ).

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

Nota: il garbage collector alla fine libererà tutti gli oggetti gestiti. Quando lo fa, chiama il metodo Finalize sull'oggetto. Il GC non sa, o cura, del tuo 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 sovrascrivendo il metodo Finalize() .

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

~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 gira su un thread in background ; non si conosce l'ordine in cui due oggetti vengono distrutti. È del tutto possibile che nel tuo codice Dispose() , l'oggetto gestito che stai cercando di eliminare (perché tu volessi essere utile) non è più lì:

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 quello che ti serve è un modo per Finalize() per dire a Dispose() che non dovrebbe toccare alcuna risorsa gestita (perché potrebbero non esserci più), pur continuando a liberare risorse non gestite.

Il modello standard per farlo è avere Finalize() e Dispose() entrambi chiamare un terzo metodo (!); dove passi un booleano che dice se lo chiami da Dispose() (al contrario di Finalize() ), significa che è sicuro liberare risorse gestite.

Questo metodo interno potrebbe avere un nome arbitrario come "CoreDispose" o "MyInternalDispose", ma è tradizione chiamarlo Dispose(Boolean) :

protected void Dispose(Boolean disposing)

Ma un nome 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 la tua implementazione del metodo IDisposable.Dispose() in:

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

e il tuo finalizzatore a:

~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 base Dispose quando esegui l'override di Dispose:

public 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() sul tuo oggetto, allora tutto è stato ripulito. Successivamente, quando il garbage collector arriva e chiama Finalize, chiamerà di nuovo Dispose .

Questo non è solo uno spreco, ma se il tuo oggetto ha riferimenti indesiderati ad oggetti che hai già eliminato dall'ultima chiamata a Dispose() , proverai a eliminarli nuovamente!

Noterai nel mio codice che stavo attento a rimuovere i riferimenti agli oggetti che ho smaltito, quindi non provo a chiamare Dispose su un riferimento a un oggetto junk. Ma questo non ha impedito a un insetto sottile di insinuarsi.

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

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

Il modo in cui risolvi questo è dire al garbage collector che non ha bisogno di disturbare la finalizzazione dell'oggetto - le sue risorse sono già state ripulite, e non è più necessario lavorare. Puoi farlo chiamando GC.SuppressFinalize() nel metodo Dispose() :

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:

  • liberato risorse non gestite
  • risorse gestite liberate

Non c'è alcun punto nel GC che esegue il finalizzatore: è tutto a posto.

Non potrei usare Finalize per ripulire le risorse non gestite?

La documentazione per Object.Finalize dice:

Il metodo Finalize viene utilizzato per eseguire operazioni di pulizia su risorse non gestite detenute 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 qual è? Qual è il posto dove posso ripulire le risorse non gestite? La risposta è:

È la vostra scelta! Ma scegli Dispose .

È certamente possibile inserire la 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 quando il garbage collector andrà in giro per finalizzare il tuo oggetto. Le tue risorse native non gestite, non necessarie e non utilizzate rimarranno attive fino a quando il garbage collector non verrà eseguito. Quindi chiamerà il metodo del finalizzatore; ripulire le risorse non gestite. La documentazione di Object.Finalize lo evidenzia:

L'ora esatta dell'esecuzione del finalizzatore non è definita. Per garantire il rilascio deterministico delle risorse per le istanze della classe, implementare un metodo Close o fornire IDisposable.Dispose .

Questo è il vantaggio dell'utilizzo di Dispose per ripulire le risorse non gestite; conosci e controlla quando la risorsa non gestita viene ripulita. La loro distruzione è "deterministica" .

Per rispondere alla tua domanda originale: perché non rilasciare memoria ora, piuttosto che quando il GC decide di farlo? Ho un software di riconoscimento facciale che ha bisogno di sbarazzarsi di 530 MB di immagini interne ora , dal momento che non sono più necessarie. Quando non lo facciamo: la macchina macina fino a fermarsi.

Lettura bonus

Per tutti quelli a cui piace lo stile di questa risposta (spiegando il perché , quindi il modo in cui diventa ovvio), ti suggerisco di leggere il Capitolo 1 della Essential COM di Don Box:

In 35 pagine spiega i problemi dell'utilizzo di oggetti binari e inventa COM davanti agli occhi. Una volta compreso il motivo della COM, le restanti 300 pagine sono ovvie e si limitano a illustrare 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.

Lettura extra bonus

Quando tutto ciò che sai è sbagliato da Eric Lippert

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


Lo scopo del pattern Dispose è quello di fornire un meccanismo per ripulire sia le risorse gestite che non gestite e quando ciò accade dipende da come viene chiamato il metodo Dispose. Nel tuo esempio, l'uso di Dispose non sta in realtà facendo nulla relativo alla disposizione, dal momento che la cancellazione di un elenco non ha alcun impatto sulla raccolta in questione. Allo stesso modo, le chiamate per impostare le variabili su null non hanno alcun impatto sul GC.

Puoi dare un'occhiata a questo article per maggiori dettagli su come implementare il pattern Dispose, ma in pratica assomiglia a questo:

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 diverse circostanze:

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

Il problema con il semplice fatto che il GC si occupi di eseguire la pulizia è che non hai un reale controllo su quando il GC eseguirà un ciclo di raccolta (puoi chiamare GC.Collect (), ma non dovresti farlo) in modo che le risorse possano rimanere più a lungo del necessario Ricorda, chiamare Dispose () non causa effettivamente un ciclo di raccolta o in alcun modo fa sì che il GC raccolga / liberi 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 lo schema di dispose non si tratta di liberare immediatamente la memoria. L'unica volta che una chiamata a Dispose avrà addirittura una possibilità di liberare immediatamente memoria è quando gestisce lo scenario == false e manipolando risorse non gestite. Per il codice gestito, la memoria non verrà effettivamente recuperata finché il GC non eseguirà un ciclo di raccolta, sul quale non si ha alcun controllo (oltre a chiamare GC.Collect (), che ho già menzionato non è una buona idea).

Lo scenario non è realmente valido poiché le stringhe in .NET non utilizzano risorse non contrassegnate e non implementano IDisposable, non c'è modo di forzarle a "ripulire".


Scenari Uso di IDisposable: ripulire le risorse non gestite, annullare l'iscrizione per gli eventi, chiudere le connessioni

L'idioma che utilizzo per l'implementazione di 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
}

Se MyCollection verrà raccolto automaticamente, quindi non dovrebbe essere necessario eliminarlo. Facendolo si limiterebbe a turbinare la CPU più del necessario e potrebbe anche invalidare alcune analisi precalcolate che il garbage collector ha già eseguito.

Uso IDisposable per fare cose come assicurarsi che i thread siano disposti correttamente, insieme alle 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 a [sic] GC.Collect () "

Concettualmente, il GC mantiene una vista del grafico di riferimento dell'oggetto e tutti i riferimenti ad esso dai frame dello stack dei thread. Questo heap può essere abbastanza grande e occupare molte pagine di memoria. Come ottimizzazione, il GC memorizza nella cache le sue pagine di pagine che è improbabile che cambino molto spesso per evitare di ripetere la pagina inutilmente. 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 cambino, ma è meno probabile in Gen1 e Gen2. Aneddoticamente, questi hook non erano disponibili in Mac OS X per il team che portava il GC su Mac per far funzionare il plug-in Silverlight su quella piattaforma.

Un altro punto contro lo smaltimento non necessario delle risorse: immagina una situazione in cui un processo sta scaricando. Immagina anche che il processo sia in esecuzione da diverso tempo. Le probabilità sono 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 stia scaricando per scambiare tutti quei dati e le code page in memoria per "rilasciare" le risorse che verranno rilasciate dal sistema operativo comunque quando il processo termina. Questo vale per le risorse gestite e persino certe non gestite. Solo le risorse che mantengono vivi i thread non di sfondo devono essere eliminate, altrimenti il ​​processo rimarrà attivo.

Ora, durante l'esecuzione normale ci sono risorse effimere che devono essere ripulite correttamente (come @fezmonkey indica connessioni di database, socket, handle di finestra ) per evitare perdite di memoria non gestite. Questi sono i tipi di cose che devono essere smaltite. Se crei una classe che possiede un thread (e per proprietà intendo che lo ha creato e quindi è responsabile di assicurarti che si fermi, almeno per 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.


IDisposable viene spesso utilizzato per sfruttare l'istruzione using 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.");
        }
    }
}

Il caso d'uso più giustificato per lo smaltimento di risorse gestite, è la preparazione per il GC per recuperare risorse che altrimenti non sarebbero mai raccolte.

Un primo esempio sono i riferimenti circolari.

Sebbene sia consigliabile utilizzare modelli che evitino riferimenti circolari, se si termina con (ad esempio) un oggetto "figlio" che ha un riferimento al suo "genitore", questo può interrompere la raccolta GC del genitore se si abbandona semplicemente il riferimento e fare affidamento su GC - più se hai implementato un finalizzatore, non verrà mai chiamato.

L'unico modo per aggirare questo è interrompere manualmente i riferimenti circolari impostando i riferimenti padre su null sui figli.

L'implementazione di IDisposable su genitori e figli è il modo migliore per farlo. Quando Dispose viene chiamato su Parent, chiama Dispose su tutti i Children e nel metodo Dispose child, imposta i riferimenti Parent su null.


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

Se gli oggetti a cui fa riferimento _theListo _theDictsono riferiti da altri oggetti, allora quell'oggetto List<>o l' Dictionary<>oggetto non sarà soggetto alla raccolta ma improvvisamente non avrà contenuti. Se non ci fosse alcuna operazione Dispose () come nell'esempio, tali raccolte conterrebbero comunque il loro contenuto.

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


Il tuo esempio di codice dato non è un buon esempio per l' IDisposableutilizzo. La cancellazione del dizionario normalmente non dovrebbe andare al Disposemetodo. Gli elementi del dizionario verranno cancellati e eliminati quando saranno fuori portata. IDisposablel'implementazione è necessaria per liberare alcuni memory / handler che non verranno rilasciati / liberi anche dopo che sono fuori portata.

L'esempio seguente mostra un buon esempio di pattern IDisposable con codice 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.
    }
}

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

Public Class LargeStuff
  Implements IDisposable
  Private _Large as string()

  'Some strange code that means _Large now contains several million long strings.

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

Mi rendo conto che l'implementazione usa e getta non segue le linee guida attuali, ma si spera che tutti ne abbiano l'idea.
Ora, quando viene chiamato Dispose, quanta memoria viene liberata?

Risposta: nessuno
Chiamare Dispose può liberare risorse non gestite, NON può recuperare memoria gestita, solo il GC può farlo. Questo non vuol dire che quanto sopra non sia una buona idea, seguire lo schema sopra è comunque una buona idea. Una volta che Dispose è stato eseguito, non c'è nulla che impedisca al GC di rivendicare la memoria che è stata utilizzata da _Large, anche se l'istanza di LargeStuff può ancora essere inclusa nell'ambito. Le stringhe in _Large possono anche essere in gen 0 ma l'istanza di LargeStuff potrebbe essere gen 2, quindi, di nuovo, la memoria verrebbe nuovamente rivendicata prima.
Non ha senso aggiungere un finalizzatore per chiamare il metodo Dispose mostrato sopra. Ritarderà solo il ri-richiedere la memoria per consentire al finalizzatore di funzionare.


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

  1. Se la classe creata da me utilizza alcune risorse non gestite, significa che dovrei anche implementare un'interfaccia IDisposable per pulire la memoria.
  2. Pulisci gli oggetti non appena ho finito di usarli.
  3. Nel mio metodo di smaltimento I iterate su tutti i membri IDisposable della classe e chiamiamo Dispose.
  4. Nel mio metodo Dispose chiama GC.SuppressFinalize (this) per comunicare al garbage collector che il mio oggetto è 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. A volte aggiungo il membro privato _disposed e controllo le chiamate del metodo con cui l'oggetto è stato ripulito. E se è stato ripulito allora genera ObjectDisposedException
    seguente modello 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;
        }
    }

Se mai, mi aspetto che il codice sia meno efficiente di quando lo si lascia fuori.

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



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

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

Per la domanda reale; se dovessi usare IDisposable per ripulire oggetti gestiti che occupano molta memoria, la risposta breve sarebbe no . Il motivo è che una volta che hai eliminato un IDisposable dovresti lasciarlo uscire dal campo di applicazione. A quel punto tutti gli oggetti figlio referenziati sono anche fuori ambito e verranno raccolti.

L'unica vera eccezione sarebbe se hai molta memoria legata in oggetti gestiti e hai bloccato quel thread in attesa di qualche operazione da completare. Se quegli oggetti dove non saranno necessari dopo che la chiamata è stata completata, 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 refactored - non un caso d'uso di IDisposable.





idisposable