Domanda

La rete Modello monouso implica che se scrivi un finalizzatore e implementi IDisposable, il tuo finalizzatore deve chiamare esplicitamente Dispose.Questo è logico ed è quello che ho sempre fatto nelle rare situazioni in cui è giustificato un finalizzatore.

Tuttavia, cosa succede se faccio semplicemente questo:

class Foo : IDisposable
{
     public void Dispose(){ CloseSomeHandle(); }
}

e non implementare un finalizzatore o altro.Il framework chiamerà il metodo Dispose per me?

Sì, mi rendo conto che sembra stupido, e tutta la logica implica che non lo sarà, ma ho sempre avuto 2 cose in testa che mi hanno reso insicuro.

  1. Qualcuno, qualche anno fa, una volta mi disse che in effetti avrebbe fatto questo, e quella persona aveva una solida esperienza nel "conoscere il fatto suo".

  2. Il compilatore/framework fa altre cose "magiche" a seconda delle interfacce implementate (ad esempio:foreach, metodi di estensione, serializzazione basata su attributi, ecc.), quindi è logico che anche questo possa essere "magico".

Anche se ho letto un sacco di cose a riguardo, e molte cose sono state implicite, non sono mai riuscito a trovare un definitivo Sì o No rispondi a questa domanda.

È stato utile?

Soluzione

Il .Net Garbage Collector chiama il metodo Object.Finalize di un oggetto nella Garbage Collection.Di predefinito questo fa Niente e deve essere sovrascritto se desideri liberare risorse aggiuntive.

Dispose NON viene chiamato automaticamente e deve esserlo esplicitamente chiamato se le risorse devono essere rilasciate, ad esempio all'interno di un blocco "using" o "provare finalmente".

Vedere http://msdn.microsoft.com/en-us/library/system.object.finalize.aspx per maggiori informazioni

Altri suggerimenti

Voglio sottolineare il punto di Brian nel suo commento, perché è importante.

I finalizzatori non sono distruttori deterministici come in C++.Come altri hanno sottolineato, non vi è alcuna garanzia di quando verrà chiamato, e in effetti se hai abbastanza memoria, se lo farà mai essere chiamato.

Ma la cosa negativa dei finalizzatori è che, come ha detto Brian, fanno sì che il tuo oggetto sopravviva a una raccolta dei rifiuti.Questo può essere negativo.Perché?

Come forse saprai o meno, il GC è suddiviso in generazioni: Gen 0, 1 e 2, più l'heap di oggetti di grandi dimensioni.Dividi è un termine vago: ottieni un blocco di memoria, ma ci sono indicazioni su dove iniziano e finiscono gli oggetti Gen 0.

Il processo di pensiero è che probabilmente utilizzerai molti oggetti che avranno vita breve.Quindi dovrebbero essere facili e veloci da raggiungere per il GC: oggetti di generazione 0.Quindi, quando c'è pressione sulla memoria, la prima cosa che fa è una raccolta Gen 0.

Ora, se ciò non risolve abbastanza pressione, allora torna indietro ed esegue uno spazzamento di Gen 1 (rifacendo Gen 0), e poi, se ancora non basta, esegue uno spazzamento di Gen 2 (rifacendo Gen 1 e Gen 0).Quindi ripulire oggetti di lunga durata può richiedere del tempo ed essere piuttosto costoso (poiché i thread potrebbero essere sospesi durante l'operazione).

Ciò significa che se fai qualcosa del genere:

~MyClass() { }

Il tuo oggetto, qualunque cosa accada, vivrà fino alla seconda generazione.Questo perché il GC non ha modo di chiamare il finalizzatore durante la garbage collection.Quindi gli oggetti che devono essere finalizzati vengono spostati in una coda speciale per essere ripuliti da un thread diverso (il thread del finalizzatore - che se uccidi fa accadere ogni genere di cose brutte).Ciò significa che i tuoi oggetti rimangono più a lungo e potenzialmente forzano più raccolte di rifiuti.

Quindi, tutto ciò è solo per portare a casa il punto che si desidera utilizzare IDisposable per ripulire le risorse quando possibile e provare seriamente a trovare modi per aggirare l'utilizzo del finalizzatore.È nel miglior interesse della tua candidatura.

Ci sono già molte discussioni interessanti qui, e sono un po' in ritardo per la festa, ma volevo aggiungere anch'io alcuni punti.

  • Il Garbage Collector non eseguirà mai direttamente un metodo Dispose per te.
  • Il G.C Volere eseguire i finalizzatori quando ne hai voglia.
  • Un modello comune utilizzato per gli oggetti che hanno un finalizzatore è quello di chiamare un metodo che è per convenzione definito come Dispose(bool disposition) passando false per indicare che la chiamata è stata effettuata a causa della finalizzazione piuttosto che di una chiamata Dispose esplicita.
  • Questo perché non è sicuro fare supposizioni su altri oggetti gestiti durante la finalizzazione di un oggetto (potrebbero essere già stati finalizzati).

class SomeObject : IDisposable {
 IntPtr _SomeNativeHandle;
 FileStream _SomeFileStream;

 // Something useful here

 ~ SomeObject() {
  Dispose(false);
 }

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

 protected virtual void Dispose(bool disposing) {
  if(disposing) {
   GC.SuppressFinalize(this);
   //Because the object was explicitly disposed, there will be no need to 
   //run the finalizer.  Suppressing it reduces pressure on the GC

   //The managed reference to an IDisposable is disposed only if the 
   _SomeFileStream.Dispose();
  }

  //Regardless, clean up the native handle ourselves.  Because it is simple a member
  // of the current instance, the GC can't have done anything to it, 
  // and this is the onlyplace to safely clean up

  if(IntPtr.Zero != _SomeNativeHandle) {
   NativeMethods.CloseHandle(_SomeNativeHandle);
   _SomeNativeHandle = IntPtr.Zero;
  }
 }
}

Questa è la versione semplice, ma ci sono molte sfumature che possono farti inciampare su questo modello.

  • Il contratto per IDisposable.Dispose indica che deve essere sicuro chiamare più volte (chiamare Dispose su un oggetto che è già stato eliminato non dovrebbe fare nulla)
  • Può diventare molto complicato gestire correttamente una gerarchia di ereditarietà di oggetti usa e getta, soprattutto se diversi livelli introducono nuove risorse monouso e non gestite.Nel modello sopra Dispose(bool) è virtuale per consentirne l'override in modo che possa essere gestito, ma trovo che sia soggetto a errori.

A mio parere, è molto meglio evitare completamente di avere tipi che contengano direttamente sia riferimenti usa e getta che risorse native che potrebbero richiedere la finalizzazione.I SafeHandle forniscono un modo molto pulito per farlo incapsulando le risorse native in oggetti usa e getta che internamente forniscono la propria finalizzazione (insieme a una serie di altri vantaggi come la rimozione della finestra durante P/Invoke in cui un handle nativo potrebbe essere perso a causa di un'eccezione asincrona) .

La semplice definizione di un SafeHandle rende tutto questo banale:


private class SomeSafeHandle
 : SafeHandleZeroOrMinusOneIsInvalid {
 public SomeSafeHandle()
  : base(true)
  { }

 protected override bool ReleaseHandle()
 { return NativeMethods.CloseHandle(handle); }
}

Consente di semplificare il tipo contenitore in:


class SomeObject : IDisposable {
 SomeSafeHandle _SomeSafeHandle;
 FileStream _SomeFileStream;
 // Something useful here
 public virtual void Dispose() {
  _SomeSafeHandle.Dispose();
  _SomeFileStream.Dispose();
 }
}

Non credo.Hai il controllo su quando viene chiamato Dispose, il che significa che in teoria potresti scrivere codice di smaltimento che presuppone (ad esempio) l'esistenza di altri oggetti.Non hai alcun controllo su quando viene chiamato il finalizzatore, quindi sarebbe incerto se il finalizzatore chiamasse automaticamente Dispose per tuo conto.


MODIFICARE:Sono andato via e ho provato, solo per essere sicuro:

class Program
{
    static void Main(string[] args)
    {
        Fred f = new Fred();
        f = null;
        GC.Collect();
        GC.WaitForPendingFinalizers();
        Console.WriteLine("Fred's gone, and he's not coming back...");
        Console.ReadLine();
    }
}

class Fred : IDisposable
{
    ~Fred()
    {
        Console.WriteLine("Being finalized");
    }

    void IDisposable.Dispose()
    {
        Console.WriteLine("Being Disposed");
    }
}

Non nel caso che descrivi, ma il GC chiamerà il Finalizzatore per te, se ne hai uno.

TUTTAVIA.Alla successiva garbage collection, invece di essere raccolto, l'oggetto entrerà nella coda di finalizzazione, tutto verrà raccolto, quindi verrà chiamato il finalizzatore.La prossima raccolta sarà liberata.

A seconda del carico di memoria della tua app, potresti non avere un gc per la generazione di quell'oggetto per un po'.Pertanto, nel caso, ad esempio, di un flusso di file o di una connessione db, potrebbe essere necessario attendere un po' prima che la risorsa non gestita venga liberata nella chiamata del finalizzatore, causando alcuni problemi.

No, non si chiama.

Ma questo rende facile non dimenticare di smaltire i tuoi oggetti.Basta usare il using parola chiave.

Per questo ho fatto il seguente test:

class Program
{
    static void Main(string[] args)
    {
        Foo foo = new Foo();
        foo = null;
        Console.WriteLine("foo is null");
        GC.Collect();
        Console.WriteLine("GC Called");
        Console.ReadLine();
    }
}

class Foo : IDisposable
{
    public void Dispose()
    {

        Console.WriteLine("Disposed!");
    }

Lo farà il GC non chiamare smaltire.Esso Maggio chiama il tuo finalizzatore, ma anche questo non è garantito in tutte le circostanze.

Guarda questo articolo per una discussione sul modo migliore per gestire la situazione.

La documentazione su Monouso fornisce una spiegazione abbastanza chiara e dettagliata del comportamento, nonché un codice di esempio.Il GC NON chiamerà il Dispose() metodo sull'interfaccia, ma chiamerà il finalizzatore per il tuo oggetto.

Il modello IDisposable è stato creato principalmente per essere chiamato dallo sviluppatore, se si dispone di un oggetto che implementa IDispose lo sviluppatore deve implementare il modello using parola chiave attorno al contesto dell'oggetto o chiamare direttamente il metodo Dispose.

Il fail-safe per il pattern consiste nell'implementare il finalizzatore che chiama il metodo Dispose().Se non lo fai potresti creare alcune perdite di memoria, ad esempio:Se crei un wrapper COM e non chiami mai System.Runtime.Interop.Marshall.ReleaseComObject(comObject) (che verrebbe inserito nel metodo Dispose).

Non c'è magia nel clr per chiamare automaticamente i metodi Dispose se non quella di tracciare gli oggetti che contengono finalizzatori e memorizzarli nella tabella Finalizer dal GC e chiamarli quando alcune euristiche di pulizia entrano in azione dal GC.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top