Uso del metodo Finalize / Dispose in C #


381

C # 2008

Ci sto lavorando da un po 'di tempo, e sono ancora confuso sull'uso di finalizzare e disporre i metodi nel codice. Le mie domande sono di seguito:

  1. So che abbiamo solo bisogno di un finalizzatore quando disponiamo di risorse non gestite. Tuttavia, se ci sono risorse gestite che effettuano chiamate a risorse non gestite, dovrebbe comunque implementare un finalizzatore?

  2. Tuttavia, se sviluppo una classe che non utilizza alcuna risorsa non gestita, direttamente o indirettamente, dovrei implementare il IDisposableper consentire ai clienti di quella classe di utilizzare la "dichiarazione using"?

    Sarebbe possibile implementare IDisposable solo per consentire ai clienti della tua classe di utilizzare l'istruzione using?

    using(myClass objClass = new myClass())
    {
        // Do stuff here
    }
  3. Ho sviluppato questo semplice codice di seguito per dimostrare l'uso di Finalize / dispose:

    public class NoGateway : IDisposable
    {
        private WebClient wc = null;
    
        public NoGateway()
        {
            wc = new WebClient();
            wc.DownloadStringCompleted += wc_DownloadStringCompleted;
        }
    
    
        // Start the Async call to find if NoGateway is true or false
        public void NoGatewayStatus()
        {
            // Start the Async's download
                // Do other work here
            wc.DownloadStringAsync(new Uri(www.xxxx.xxx));
        }
    
        private void wc_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
        {
            // Do work here
        }
    
        // Dispose of the NoGateway object
        public void Dispose()
        {
            wc.DownloadStringCompleted -= wc_DownloadStringCompleted;
            wc.Dispose();
            GC.SuppressFinalize(this);
        }
    }

Domanda sul codice sorgente:

  1. Qui non ho aggiunto il finalizzatore e normalmente il finalizzatore verrà chiamato dal GC e il finalizzatore chiamerà Dispose. Dato che non ho il finalizzatore, quando chiamo il metodo Dispose? È il client della classe che deve chiamarlo?

    Quindi la mia classe nell'esempio si chiama NoGateway e il client potrebbe usare e smaltire la classe in questo modo:

    using(NoGateway objNoGateway = new NoGateway())
    {
        // Do stuff here   
    }

    Il metodo Dispose verrebbe automaticamente chiamato quando l'esecuzione raggiunge la fine del blocco using o il client deve chiamare manualmente il metodo dispose? vale a dire

    NoGateway objNoGateway = new NoGateway();
    // Do stuff with object
    objNoGateway.Dispose(); // finished with it
  2. Sto usando la WebClientclasse nella mia NoGatewayclasse. Perché WebClientimplementa l' IDisposableinterfaccia, questo significa che WebClientutilizza indirettamente risorse non gestite? C'è una regola dura e veloce da seguire? Come faccio a sapere che una classe utilizza risorse non gestite?


1
questo complicato modello di progettazione è effettivamente richiesto per risolvere questo problema di ricollocamento delle risorse?
tintinnio il

Risposte:


422

Il modello IDisposable raccomandato è qui . Quando si programma una classe che utilizza IDisposable, in genere è necessario utilizzare due modelli:

Quando si implementa una classe sigillata che non utilizza risorse non gestite, è sufficiente implementare un metodo Dispose come con le normali implementazioni dell'interfaccia:

public sealed class A : IDisposable
{
    public void Dispose()
    {
        // get rid of managed resources, call Dispose on member variables...
    }
}

Quando si implementa una classe non sigillata, farlo in questo modo:

public class B : IDisposable
{    
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (disposing)
        {
            // get rid of managed resources
        }   
        // get rid of unmanaged resources
    }

    // only if you use unmanaged resources directly in B
    //~B()
    //{
    //    Dispose(false);
    //}
}

Si noti che non ho dichiarato un finalizzatore in B; è necessario implementare un finalizzatore solo se si dispone di risorse non gestite effettive da smaltire. Il CLR tratta gli oggetti finalizzabili in modo diverso dagli oggetti non finalizzabili, anche se SuppressFinalizeviene chiamato.

Quindi, non dovresti dichiarare un finalizzatore a meno che non sia necessario, ma dai un gancio agli eredi della tua classe per chiamare il tuo Disposee implementare un finalizzatore se usano direttamente risorse non gestite:

public class C : B
{
    private IntPtr m_Handle;

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            // get rid of managed resources
        }
        ReleaseHandle(m_Handle);

        base.Dispose(disposing);
    }

    ~C() {
        Dispose(false);
    }
}

Se non stai utilizzando direttamente risorse non gestite ( SafeHandlee gli amici non contano, poiché dichiarano i propri finalizzatori), allora non implementare un finalizzatore, poiché il GC gestisce le classi finalizzabili in modo diverso, anche se successivamente sopprimi il finalizzatore. Si noti inoltre che, sebbene Bnon disponga di un finalizzatore, richiede comunque SuppressFinalizedi gestire correttamente tutte le sottoclassi che implementano un finalizzatore.

Quando una classe implementa l'interfaccia IDisposable, significa che da qualche parte ci sono alcune risorse non gestite che dovrebbero essere eliminate quando hai finito di usare la classe. Le risorse effettive sono incapsulate all'interno delle classi; non è necessario eliminarli esplicitamente. La semplice chiamata Dispose()o il wrapping della classe in a using(...) {}assicurerà che tutte le risorse non gestite vengano eliminate, se necessario.


26
Sono d'accordo con thecoop. Nota che non hai bisogno di un finalizzatore se hai a che fare solo con risorse gestite (in effetti, NON dovresti provare ad accedere agli oggetti gestiti dal tuo finalizzatore (diverso da "questo"), perché non esiste un ordine garantito in cui il GC ripulirà gli oggetti Inoltre, se stai usando .Net 2.0 o superiore, puoi (e dovresti) usare SafeHandles per avvolgere gli handle non gestiti. Gli handle sicuri riducono notevolmente la necessità di scrivere finalizzatori per le tue classi gestite. Blogs.msdn. com / bclteam / archive / 2005/03/16 / 396900.aspx
JMarsch,

5
Penso che sia meglio effettuare una chiamata a MessageBox.Show ("Errore", + GetType (). Nome + "non disposto") nel finalizzatore, poiché l'oggetto usa e getta dovrebbe SEMPRE essere eliminato, e se non lo fai è meglio essere avvisati del fatto il prima possibile.
erikkallen,

95
@erikkallen è uno scherzo? :)
Alex Norcliffe,

2
poiché è necessario uno sforzo di elaborazione aggiuntivo nel CLR per tenere traccia delle lezioni con finalizzatori attivi. - L'implementazione di un finalizzatore fa sì che ciò accada. Chiamare GC.SuppressFinalize significa che Finalizer non deve essere chiamato dal runtime. Va comunque Gen2 a prescindere. Non aggiungere un finalizzatore se non hai a che fare con risorse gestite. I modificatori di classe sigillati o non sigillati sono irrilevanti a questo punto.
Ritch Melton,

3
@Ritch: citazione? Non è necessariamente una brutta cosa; se stai implementando IDisposable, è probabile che rimarrà in sospeso per un po 'comunque. Stai salvando il CLR lo sforzo di doverlo copiare da Gen0 -> Gen1 -> Gen2
thecoop

123

Il modello ufficiale da implementare IDisposableè difficile da capire. Credo che questo sia migliore :

public class BetterDisposableClass : IDisposable {

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

  protected virtual void CleanUpManagedResources() { 
    // ...
  }
  protected virtual void CleanUpNativeResources() {
    // ...
  }

  ~BetterDisposableClass() {
    CleanUpNativeResources();
  }

}

Una soluzione ancora migliore è avere una regola che devi sempre creare una classe wrapper per qualsiasi risorsa non gestita che devi gestire:

public class NativeDisposable : IDisposable {

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

  protected virtual void CleanUpNativeResource() {
    // ...
  }

  ~NativeDisposable() {
    CleanUpNativeResource();
  }

}

Con SafeHandlee suoi derivati, queste classi dovrebbero essere molto rare .

Il risultato per le classi usa e getta che non si occupano direttamente delle risorse non gestite, anche in presenza di eredità, è potente: non devono più occuparsi delle risorse non gestite . Saranno semplici da implementare e da capire:

public class ManagedDisposable : IDisposable {

  public virtual void Dispose() {
    // dispose of managed resources
  }

}

@Kyle: grazie! Mi piace troppo :-) C'è un follow-up ad esso qui .
Jordão,

4
Anche se una cosa che voglio notare è che non impedisce di essere chiamato la seconda volta.
HuseyinUslu,

5
@HuseyinUslu: questa è solo l' essenza del modello. Puoi sicuramente aggiungere una disposedbandiera e controllare di conseguenza.
Jordão,

2
@didibus: è una semplice questione di aggiungere un disposedflag, controllarlo prima di smaltirlo e impostarlo dopo lo smaltimento. Guarda qui l'idea. Dovresti anche controllare la bandiera prima di qualsiasi metodo della classe. Ha senso? È complicato?
Jordão,

1
+1 per "Una soluzione ancora migliore è avere una regola che devi sempre creare una classe wrapper per qualsiasi risorsa non gestita che devi gestire" . Mi sono imbattuto in questo in un addon per VLC e lo uso da allora. Salva così tanti mal di testa ...
Franz B.

37

Si noti che qualsiasi implementazione IDisposable dovrebbe seguire il modello seguente (IMHO). Ho sviluppato questo modello basandomi sulle informazioni di diversi "dei" .NET . Le Linee guida per la progettazione di .NET Framework sono state scritte da Krzysztof Cwalina (all'epoca CLR Architect) e Brad Abrams (credo all'epoca Program Manager del CLR) e Bill Wagner ([C # efficace] e [C # più efficace]) cercali su Amazon.com:

Nota che NON devi MAI implementare un Finalizer a meno che la tua classe non contenga direttamente (non eredita) risorse UNmanaged. Una volta implementato un Finalizer in una classe, anche se non viene mai chiamato, è garantito che vivrà per una raccolta aggiuntiva. Viene automaticamente inserito nella coda di finalizzazione (che viene eseguita su un singolo thread). Inoltre, una nota molto importante ... tutto il codice eseguito all'interno di un Finalizer (nel caso dovessi averne bisogno) DEVE essere thread-safe ed eccezionalmente sicuro! Le cose cattive accadranno altrimenti ... (cioè comportamento indeterminato e, in caso di eccezione, un incidente irreversibile irreversibile fatale).

Il modello che ho messo insieme (e per cui ho scritto uno snippet di codice) segue:

#region IDisposable implementation

//TODO remember to make this class inherit from IDisposable -> $className$ : IDisposable

// Default initialization for a bool is 'false'
private bool IsDisposed { get; set; }

/// <summary>
/// Implementation of Dispose according to .NET Framework Design Guidelines.
/// </summary>
/// <remarks>Do not make this method virtual.
/// A derived class should not be able to override this method.
/// </remarks>
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.

    // Always use SuppressFinalize() in case a subclass
    // of this type implements a finalizer.
    GC.SuppressFinalize( this );
}

/// <summary>
/// Overloaded Implementation of Dispose.
/// </summary>
/// <param name="isDisposing"></param>
/// <remarks>
/// <para><list type="bulleted">Dispose(bool isDisposing) executes in two distinct scenarios.
/// <item>If <paramref name="isDisposing"/> equals true, the method has been called directly
/// or indirectly by a user's code. Managed and unmanaged resources
/// can be disposed.</item>
/// <item>If <paramref name="isDisposing"/> 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.</item></list></para>
/// </remarks>
protected virtual void Dispose( bool isDisposing )
{
    // TODO If you need thread safety, use a lock around these 
    // operations, as well as in your methods that use the resource.
    try
    {
        if( !this.IsDisposed )
        {
            if( isDisposing )
            {
                // TODO Release all managed resources here

                $end$
            }

            // TODO Release all unmanaged resources here



            // TODO explicitly set root references to null to expressly tell the GarbageCollector
            // that the resources have been disposed of and its ok to release the memory allocated for them.


        }
    }
    finally
    {
        // explicitly call the base class Dispose implementation
        base.Dispose( isDisposing );

        this.IsDisposed = true;
    }
}

//TODO Uncomment this code if this class will contain members which are UNmanaged
// 
///// <summary>Finalizer for $className$</summary>
///// <remarks>This finalizer will run only if the Dispose method does not get called.
///// It gives your base class the opportunity to finalize.
///// DO NOT provide finalizers in types derived from this class.
///// All code executed within a Finalizer MUST be thread-safe!</remarks>
//  ~$className$()
//  {
//     Dispose( false );
//  }
#endregion IDisposable implementation

Ecco il codice per l'implementazione di IDisposable in una classe derivata. Si noti che non è necessario elencare esplicitamente l'ereditarietà da IDisposable nella definizione della classe derivata.

public DerivedClass : BaseClass, IDisposable (remove the IDisposable because it is inherited from BaseClass)


protected override void Dispose( bool isDisposing )
{
    try
    {
        if ( !this.IsDisposed )
        {
            if ( isDisposing )
            {
                // Release all managed resources here

            }
        }
    }
    finally
    {
        // explicitly call the base class Dispose implementation
        base.Dispose( isDisposing );
    }
}

Ho pubblicato questa implementazione sul mio blog all'indirizzo: Come implementare correttamente il modello di smaltimento


Qualcuno può anche aggiungere un modello per una classe derivata (derivante da questa classe base)
akjoshi il

3
@akjoshi - Ho aggiornato il modello sopra per includere il codice per una classe usa e getta derivata. Inoltre, non implementare MAI un Finalizzatore in una classe derivata ...
Dave Black

3
A Microsoft sembra piacere impostare il flag "disposto" alla fine del metodo eliminato, ma questo mi sembra sbagliato. Le chiamate ridondanti a "Dispose" non dovrebbero fare nulla; mentre normalmente non ci si aspetterebbe che Dispose venga chiamato in modo ricorsivo, cose del genere potrebbero accadere se si sta cercando di smaltire un oggetto che è stato lasciato in uno stato non valido a causa di un'eccezione verificatasi durante la costruzione o altre operazioni. Penserei che usare un flag Interlocked.Exchangesu un numero intero IsDisposednella funzione wrapper non virtuale sarebbe più sicuro.
supercat,

@DaveBlack: cosa succede se la tua classe di base non utilizza risorse non gestite, ma la tua classe derivata fa? Quindi devi implementare Finalizer nella classe derivata? E se sì, come fai a sapere che la classe base non l'ha già implementata se non hai accesso al sorgente?
Didier A.

@DaveBlack "Ho sviluppato questo schema basandomi sulle informazioni di diversi" dei ".NET." Se uno degli dei fosse Jon Skeet, seguirò il tuo consiglio.
Elisabeth,

23

Sono d'accordo con pm100 (e avrei dovuto dirlo esplicitamente nel mio post precedente).

Non dovresti mai implementare IDisposable in una classe se non ne hai bisogno. Per essere molto specifici, ci sono circa 5 volte in cui avresti mai bisogno / dovresti implementare IDisposable:

  1. La tua classe contiene esplicitamente (cioè non tramite ereditarietà) tutte le risorse gestite che implementano IDisposable e dovrebbero essere ripulite una volta che la tua classe non è più utilizzata. Ad esempio, se la tua classe contiene un'istanza di Stream, DbCommand, DataTable, ecc.

  2. La tua classe contiene esplicitamente qualsiasi risorsa gestita che implementa un metodo Close () - ad esempio IDataReader, IDbConnection, ecc. Nota che alcune di queste classi implementano IDisposable avendo Dispose () e un metodo Close ().

  3. La tua classe contiene esplicitamente una risorsa non gestita, ad esempio un oggetto COM, puntatori (sì, puoi usare i puntatori in C # gestito ma devono essere dichiarati in blocchi "non sicuri", ecc. Nel caso di risorse non gestite, dovresti anche assicurarti di chiamare System.Runtime.InteropServices.Marshal.ReleaseComObject () sull'RCW. Anche se l'RCW è, in teoria, un wrapper gestito, esiste ancora un conteggio dei riferimenti in corso sotto le copertine.

  4. Se la tua classe si iscrive agli eventi usando riferimenti forti. Devi annullare la registrazione / staccarti dagli eventi. Assicurati sempre che questi non siano nulli prima di provare a annullare la registrazione / scollegarli !.

  5. La tua classe contiene qualsiasi combinazione di quanto sopra ...

Un'alternativa consigliata all'utilizzo degli oggetti COM e alla necessità di utilizzare Marshal.ReleaseComObject () è utilizzare la classe System.Runtime.InteropServices.SafeHandle.

Il BCL (Base Class Library Team) ha un buon post sul blog qui http://blogs.msdn.com/bclteam/archive/2005/03/16/396900.aspx

Una nota molto importante da prendere è che se si lavora con WCF e si puliscono le risorse, si dovrebbe SEMPRE SEMPRE evitare il blocco "utilizzo". Ci sono molti post sul blog e alcuni su MSDN sul perché questa è una cattiva idea. Ho anche pubblicato un post qui - Non usare 'using ()' con un proxy WCF


3
Credo che ci sia un quinto caso: se la tua classe si iscrive a eventi usando riferimenti forti, allora dovresti implementare IDisposable e annullare la registrazione dagli eventi nel metodo Dispose.
Didier A.

Ciao didibus. Sì hai ragione. Mi ero dimenticato di quello. Ho modificato la mia risposta per includerla come caso. Grazie.
Dave Black,

La documentazione MSDN per il modello dispose aggiunge un altro caso: "CONSIDERA l'implementazione del modello Dispose di base su classi che a loro volta non contengono risorse non gestite o oggetti usa e getta ma che probabilmente hanno sottotipi che lo fanno. Un ottimo esempio di ciò è il System.IO .Stream class. Sebbene sia una classe base astratta che non contiene alcuna risorsa, la maggior parte delle sue sottoclassi lo fanno e per questo motivo, implementa questo modello. "
Gonen I,

12

Utilizzo di lambda anziché IDisposable.

Non sono mai stato elettrizzato dall'idea che utilizza / IDisposable. Il problema è che richiede al chiamante di:

  • sanno che devono usare IDisposable
  • ricordati di usare 'using'.

Il mio nuovo metodo preferito è usare invece un metodo factory e un lambda

Immagina di voler fare qualcosa con un SqlConnection (qualcosa che dovrebbe essere racchiuso in un utilizzo). Classicamente lo faresti

using (Var conn = Factory.MakeConnection())
{
     conn.Query(....);
}

Nuovo modo

Factory.DoWithConnection((conn)=>
{
    conn.Query(...);
}

Nel primo caso il chiamante non poteva semplicemente usare la sintassi di utilizzo. Nel secondo caso l'utente non ha scelta. Non esiste un metodo che crea un oggetto SqlConnection, il chiamante deve invocare DoWithConnection.

DoWithConnection è simile a questo

void DoWithConnection(Action<SqlConnection> action)
{
   using (var conn = MakeConnection())
   {
       action(conn);
   }
}

MakeConnection è ora privato


2
Avvolgere le cose in lambda può essere un buon approccio, ma ha dei limiti. Non è male per le situazioni in cui, in effetti, tutti i consumatori di una classe impiegerebbero un blocco "using", ma impedirebbero situazioni in cui un metodo memorizzerebbe un IDisposable in un campo di classe (direttamente o in qualcosa come un iteratore ).
Supercat,

@supercat puoi sostenere che vietare l'archiviazione di cose che fanno il hogging delle risorse è una buona cosa. Il modello di prestito che propongo qui ti costringe ad essere snello con il tuo utilizzo della risorsa
pm100

Può essere una buona cosa, ma può anche rendere alcune operazioni molto ragionevoli molto difficili. Ad esempio, supponiamo che un tipo di lettore di database, invece di implementare IEnumerable <T>, esponga un metodo DoForAll(Action<T>) where T:IComparable<T>, chiamando il delegato indicato su ciascun record. Dati due di questi oggetti, entrambi i quali restituiranno i dati in ordine ordinato, come potrebbero essere prodotti tutti gli elementi presenti in una raccolta ma non nell'altro? Se i tipi implementati IEnumerable<T>, si potrebbe eseguire un'operazione di fusione, ma non funzionerà DoForAll.
supercat

L'unico modo in cui riesco a unire due DoForAllraccolte senza dover prima copiarne una, nella sua interezza, in un'altra struttura, sarebbe quello di utilizzare due thread, che sarebbero piuttosto più noiosi di risorse rispetto al semplice utilizzo di una coppia di IEnumerable e facendo attenzione per liberarli.
supercat

-1: buona risposta a una domanda che non è stata posta. Questa sarebbe un'ottima risposta a "come posso semplificare il consumo di oggetti IDisposable"
John Saunders,

10

nessuno ha risposto alla domanda se dovresti implementare IDisposable anche se non ne hai bisogno.

Risposta breve: No

Risposta lunga:

Ciò consentirebbe a un consumatore della tua classe di utilizzare "utilizzo". La domanda che vorrei porre è: perché dovrebbero farlo? La maggior parte degli sviluppatori non userà 'usare' a meno che non sappiano che devono - e come fanno a saperlo. O

  • è ovvio che li per esperienza (una classe socket per esempio)
  • è documentato
  • sono cauti e possono vedere che la classe implementa IDisposable

Quindi implementando IDisposable stai dicendo agli sviluppatori (almeno alcuni) che questa classe racchiude qualcosa che deve essere rilasciato. Useranno "utilizzo" - ma ci sono altri casi in cui l'utilizzo non è possibile (l'ambito dell'oggetto non è locale); e dovranno iniziare a preoccuparsi della vita degli oggetti in quegli altri casi - mi preoccuperei di sicuro. Ma questo non è necessario

Implementate Idisposable per consentirgli di usare using, ma non useranno use a meno che non glielo dica.

Quindi non farlo


1
Non capisco perché uno sviluppatore non usi / disponga su un oggetto che implementa IDisposable (a meno che il programma non stia per uscire comunque).
Adrianm,

1
il punto è che uno sviluppatore dovrebbe scrivere tutte le chiamate per disporre in tutti i percorsi di codice che si traducono nella sua non referenziazione. Quindi, ad esempio, se inserisco un'istanza in un dizionario, quando cancello voci dal dizionario, devo chiamare dispose. È un sacco di seccatura che non è necessaria in questo caso - l'oggetto non ha bisogno di essere smaltito
pm100

3
@ pm100 Re: Implementazione inutile di IDisposable - C'è un articolo dettagliato su codeproject.com/KB/dotnet/idisposable.aspx che discute alcuni casi rari in cui potresti voler pensare a questo (molto raro, ne sono sicuro). In breve: se puoi prevedere la necessità di IDisposable in futuro o in un oggetto derivato, potresti pensare di implementare IDisposable come "no-op" nella tua classe base per evitare problemi di "suddivisione" in cui alcuni oggetti derivati ​​richiedono smaltimento e altri no.
Kevin P. Rice,

4
  1. Se si utilizzano altri oggetti gestiti che utilizzano risorse non gestite, non è responsabilità dell'utente assicurarsi che vengano finalizzati. La tua responsabilità è chiamare Dispose su quegli oggetti quando Dispose viene chiamato sul tuo oggetto e si ferma lì.

  2. Se la tua classe non utilizza risorse scarse, non riesco a capire perché dovresti rendere IDisposable la tua classe implementabile. Dovresti farlo solo se sei:

    • Sappi che presto avrai scarse risorse nei tuoi oggetti, non ora (e intendo che come "stiamo ancora sviluppando, sarà qui prima di aver finito", non come in "Penso che avremo bisogno di questo ")
    • Utilizzando scarse risorse
  3. Sì, il codice che utilizza il tuo codice deve chiamare il metodo Dispose dell'oggetto. E sì, il codice che utilizza il tuo oggetto può usare usingcome hai mostrato.

  4. (Di nuovo 2?) È probabile che WebClient utilizzi risorse non gestite o altre risorse gestite che implementano IDisposable. Il motivo esatto, tuttavia, non è importante. Ciò che è importante è che implementa IDisposable, e quindi spetta a te agire su quella conoscenza eliminando l'oggetto quando hai finito con esso, anche se si scopre che WebClient non utilizza altre risorse.


4

Smaltire modello:

public abstract class DisposableObject : IDisposable
{
    public bool Disposed { get; private set;}      

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

    ~DisposableObject()
    {
        Dispose(false);
    }

    private void Dispose(bool disposing)
    {
        if (!Disposed)
        {
            if (disposing)
            {
                DisposeManagedResources();
            }

            DisposeUnmanagedResources();
            Disposed = true;
        }
    }

    protected virtual void DisposeManagedResources() { }
    protected virtual void DisposeUnmanagedResources() { }
}

Esempio di eredità:

public class A : DisposableObject
{
    public Component components_a { get; set; }
    private IntPtr handle_a;

    protected override void DisposeManagedResources()
    {
        try
        {
          Console.WriteLine("A_DisposeManagedResources");
          components_a.Dispose();
          components_a = null;
        }
        finally
        { 
          base.DisposeManagedResources();
        }
    }

    protected override void DisposeUnmanagedResources()
    {
        try
        {
          Console.WriteLine("A_DisposeUnmanagedResources");
          CloseHandle(handle_a);
          handle_a = IntPtr.Zero;
        }
        finally
        { 
          base.DisposeUnmanagedResources();
        }
    }
}

public class B : A
{
    public Component components_b { get; set; }
    private IntPtr handle_b;

    protected override void DisposeManagedResources()
    {
        try
        {
          Console.WriteLine("B_DisposeManagedResources");
          components_b.Dispose();
          components_b = null;
        }
        finally
        { 
          base.DisposeManagedResources();
        }
    }

    protected override void DisposeUnmanagedResources()
    {
        try
        {
          Console.WriteLine("B_DisposeUnmanagedResources");
          CloseHandle(handle_b);
          handle_b = IntPtr.Zero;
        }
        finally
        { 
          base.DisposeUnmanagedResources();
        }
    }
}

4

Alcuni aspetti di un'altra risposta sono leggermente errati per 2 motivi:

Primo,

using(NoGateway objNoGateway = new NoGateway())

in realtà equivale a:

try
{
    NoGateway = new NoGateway();
}
finally
{
    if(NoGateway != null)
    {
        NoGateway.Dispose();
    }
}

Ciò può sembrare ridicolo poiché il "nuovo" operatore non dovrebbe mai restituire "null" a meno che non si abbia un'eccezione OutOfMemory. Considerare i seguenti casi: 1. Si chiama FactoryClass che restituisce una risorsa IDisposable o 2. Se si dispone di un tipo che può o non può ereditare da IDisposable a seconda della sua implementazione, ricordare che ho visto il modello IDisposable implementato in modo errato molti volte in molti client in cui gli sviluppatori aggiungono semplicemente un metodo Dispose () senza ereditare da IDisposable (cattivo, cattivo, cattivo). Potresti anche avere il caso in cui una risorsa IDisposable venga restituita da una proprietà o un metodo (di nuovo bad, bad, bad - non dare via le tue risorse IDisposable)

using(IDisposable objNoGateway = new NoGateway() as IDisposable)
{
    if (NoGateway != null)
    {
        ...

Se l'operatore "as" restituisce null (o proprietà o metodo che restituisce la risorsa) e il codice nel blocco "using" protegge da "null", il codice non verrà espulso quando si tenta di chiamare Dispose su un oggetto null a causa di il controllo null 'incorporato'.

Il secondo motivo per cui la tua risposta non è precisa è a causa del seguente stmt:

Viene chiamato un finalizzatore sul GC che distrugge il tuo oggetto

Innanzitutto, la finalizzazione (così come GC stesso) non è deterministica. Il CLR determina quando chiamerà un finalizzatore. cioè lo sviluppatore / codice non ha idea. Se il modello IDisposable è implementato correttamente (come ho scritto sopra) e GC.SuppressFinalize () è stato chiamato, il Finalizzatore NON verrà chiamato. Questo è uno dei motivi principali per implementare correttamente il modello in modo corretto. Poiché esiste un solo thread Finalizer per processo gestito, indipendentemente dal numero di processori logici, è possibile degradare facilmente le prestazioni eseguendo il backup o persino appendendo il thread Finalizer dimenticando di chiamare GC.SuppressFinalize ().

Ho pubblicato un'implementazione corretta del modello Dispose sul mio blog: Come implementare correttamente il modello Dispose


2
Sei sicuro di scrivere NoGateway = new NoGateway();e NoGateway != null?
Cor

1
Si riferiva a stackoverflow.com/a/898856/3195477 ? Non c'è nessuna risposta ora pubblicata con il nome 'Icey'
UuDdLrLrSs

@DaveInCaz sembra che sia corretto. Non vedo 'Icey' da nessuna parte ma il contesto della mia risposta sembra essere diretto alla risposta fornita dal tuo link sopra. Forse ha cambiato il suo nome utente?
Dave Black,

@DaveBlack cool, grazie. L'ho appena modificato nel testo.
UuDdLrLrSs

2

1) WebClient è un tipo gestito, quindi non è necessario un finalizzatore. Il finalizzatore è necessario nel caso in cui i tuoi utenti non dispongano () della tua classe NoGateway e il tipo nativo (che non è raccolto dal GC) deve essere ripulito dopo. In questo caso, se l'utente non chiama Dispose (), il WebClient contenuto verrà eliminato dal GC subito dopo NoGateway.

2) Indirettamente sì, ma non dovresti preoccuparti. Il tuo codice è corretto e non puoi impedire ai tuoi utenti di dimenticare facilmente Dispose ().


2

Pattern da msdn

public class BaseResource: IDisposable
{
   private IntPtr handle;
   private Component Components;
   private bool disposed = false;
   public BaseResource()
   {
   }
   public void Dispose()
   {
      Dispose(true);      
      GC.SuppressFinalize(this);
   }
   protected virtual void Dispose(bool disposing)
   {
      if(!this.disposed)
      {        
         if(disposing)
         {
            Components.Dispose();
         }         
         CloseHandle(handle);
         handle = IntPtr.Zero;
       }
      disposed = true;         
   }
   ~BaseResource()      
   {      Dispose(false);
   }
   public void DoSomething()
   {
      if(this.disposed)
      {
         throw new ObjectDisposedException();
      }
   }
}
public class MyResourceWrapper: BaseResource
{
   private ManagedResource addedManaged;
   private NativeResource addedNative;
   private bool disposed = false;
   public MyResourceWrapper()
   {
   }
   protected override void Dispose(bool disposing)
   {
      if(!this.disposed)
      {
         try
         {
            if(disposing)
            {             
               addedManaged.Dispose();         
            }
            CloseHandle(addedNative);
            this.disposed = true;
         }
         finally
         {
            base.Dispose(disposing);
         }
      }
   }
}

1
using(NoGateway objNoGateway = new NoGateway())

è equivalente a

try
{
    NoGateway = new NoGateway();
}

finally
{
    NoGateway.Dispose();
}

Viene chiamato un finalizzatore sul GC che distrugge il tuo oggetto. Questo può avvenire in un momento completamente diverso rispetto a quando lasci il tuo metodo. L'eliminazione di IDisposable viene chiamata immediatamente dopo aver lasciato il blocco using. Quindi lo schema è di solito usare usando per liberare risorse immediatamente dopo che non ne hai più bisogno.


1
Un finalizzatore non viene chiamato dal GC che distrugge l'oggetto. Se "Finalizza" viene sovrascritto, quando il GC avrebbe altrimenti distrutto l'oggetto , verrà posizionato su una coda di oggetti che necessitano di finalizzazione, creando temporaneamente un forte riferimento ad esso e - almeno temporaneamente - "resuscitandolo".
supercat,

-5

Da quello che so, si consiglia vivamente di NON utilizzare Finalizer / Destructor:

public ~MyClass() {
  //dont use this
}

Principalmente, ciò è dovuto al fatto di non sapere quando o se verrà chiamato. Il metodo di smaltimento è molto migliore, soprattutto se si utilizza o si smaltisce direttamente.

usare è buono. usalo :)


2
Dovresti seguire il link nella risposta di thecoop. Sì, usare / Dispose è migliore ma una classe usa e getta dovrebbe sicuramente implementare entrambi.
Henk Holterman,

Interessante, tutti i documenti che ho letto da Microsoft - ad esempio le linee guida per la progettazione del framework - dicono che NON usare MAI un distruttore. Usa sempre IDisposable.
Nic Wise,

5
Basta distinguere tra l' uso di una classe e la scrittura della classe, leggendoli di nuovo.
Henk Holterman,

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.