Domanda

C#2008

Ci sto lavorando da un po' ormai e sono ancora confuso su alcuni problemi.Le mie domande sono qui sotto

  1. So che hai bisogno di un finalizzatore solo se stai smaltindo risorse non gestite.Tuttavia, se utilizzi risorse gestite che effettuano chiamate a risorse non gestite, sarebbe comunque necessario implementare un finalizzatore?

  2. Tuttavia, se sviluppi una classe che non utilizza risorse non gestite, direttamente o indirettamente, puoi implementare il file IDisposable in modo che i client della tua classe possano utilizzare l'istruzione using?

    Sarebbe accettabile implementare IDisposable solo in modo che i client della tua classe possano utilizzare l'istruzione using?

    using(myClass objClass = new myClass())
    {
        // Do stuff here
    }
    
  3. Ho sviluppato questo semplice codice di seguito per dimostrare il modello 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 utilizzare ed eliminare la classe in questo modo:

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

    Il metodo Dispose verrà chiamato automaticamente quando l'esecuzione raggiunge la fine del blocco using oppure il client dovrà chiamare manualmente il metodo Dispose?cioè.

    NoGateway objNoGateway = new NoGateway();
    // Do stuff with object
    objNoGateway.Dispose(); // finished with it
    
  2. Sto usando la classe webclient nel mio file NoGateway classe.Poiché il client Web implementa l'interfaccia IDisposable, ciò significa che il client Web utilizza indirettamente risorse non gestite?C'è una regola ferrea da seguire a riguardo?Come faccio a sapere se una classe utilizza risorse non gestite?

È stato utile?

Soluzione

Il IDisposable modello consigliato è qui . Quando si programma una classe che utilizza IDisposable, in genere si consiglia di 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 di interfacce:

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, fare 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; si dovrebbe implementare solo un finalizzatore, se si dispone di effettive risorse non gestite da smaltire. Le offerte CLR con oggetti finalizable modo diverso per oggetti non finalizable, anche se SuppressFinalize viene chiamato.

Quindi, non si dovrebbe dichiarare un finalizzatore a meno che non si deve, ma dare eredi della vostra classe un gancio per chiamare il Dispose e implementare un finalizzatore stessi se usano risorse non gestite direttamente:

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 si utilizza direttamente le risorse non gestite (SafeHandle e gli amici non conta, come dichiarano i propri finalizzatori), allora non implementare un finalizzatore, come le offerte GC con le classi finalizable modo diverso, anche se più tardi sopprimere il finalizzatore. Si noti inoltre che, anche se B non ha un finalizzatore, si chiama ancora SuppressFinalize per affrontare correttamente eventuali sottoclassi che implementare un finalizzatore.

Quando una classe implementa l'interfaccia IDisposable, significa che da qualche parte ci sono alcune risorse non gestite che dovrebbero essere è sbarazzato di quando hai finito di utilizzare la classe. Le risorse effettive sono incapsulati all'interno delle classi; non c'è bisogno di cancellarli in modo esplicito. Semplicemente chiamando Dispose() o avvolgendo la classe in un using(...) {} farà in modo che tutte le risorse non gestite sono sbarazzati di quanto necessario.

Altri suggerimenti

Il modello ufficiale per implementare IDisposable è difficile da capire. Credo che questo è meglio :

public class BetterDisposableClass : IDisposable {

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

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

  ~BetterDisposableClass() {
    CleanUpNativeResources();
  }

}

Un href="http://codecrafter.blogspot.com/2010/01/revisiting-idisposable.html" rel="noreferrer"> soluzione è quella di avere una regola che si < strong> sempre è necessario creare una classe wrapper per qualsiasi risorsa non gestita che è necessario gestire:

public class NativeDisposable : IDisposable {

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

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

  ~NativeDisposable() {
    CleanUpNativeResource();
  }

}

Con SafeHandle e suoi derivati, queste classi dovrebbero essere molto raro .

Il risultato per le classi usa e getta che non si occupano direttamente con risorse non gestite, anche in presenza di eredità, è potente: non hanno bisogno di essere interessato con risorse non gestite più . Saranno semplice da implementare e da capire:

public class ManagedDisposable : IDisposable {

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

}

Si noti che qualsiasi applicazione IDisposable dovrebbe seguire il modello di seguito (IMHO). Ho sviluppato questo modello sulla base di informazioni da diversi ottimi NET "dei" .NET Framework Design Guidelines (si noti che MSDN non segue questo per qualche motivo!). Il quadro Linee guida di progettazione .NET sono state scritte da Krzysztof Cwalina (CLR architetto al momento) e Brad Abrams (credo che il direttore CLR programma al momento) e Bill Wagner ([Effective C #] e [più efficace C #] (basta prendere un cercare questi su Amazon.com:

Si noti che non si dovrebbe mai implementare un finalizzatore a meno che la classe contiene direttamente (non eredita) risorse non gestite. Una volta che si sceglie di implementare un finalizzatore in una classe, anche se non viene mai chiamato, è garantito a vivere per una collezione in più. Viene automaticamente messa in coda di terminazione (che gira su un singolo filo). Inoltre, una nota molto importante ... tutto il codice eseguito all'interno di un finalizzatore (se avete bisogno di implementare uno) DEVE essere thread-safe e sicuro rispetto alle eccezioni! accadranno brutte cose altrimenti ... (cioè. il comportamento indeterminato e, nel caso di un'eccezione, un crash irreversibile fatale).

Il modello che ho messo insieme (e scritto un frammento di codice per) 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 implementare IDisposable in una classe derivata. Si noti che è non è necessario elencare esplicitamente eredità 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 postato questa implementazione sul mio blog all'indirizzo: Come implementare correttamente il pattern Dispose

Sono d'accordo con PM100 (e avrebbe dovuto dire in modo esplicito questo nel mio precedente post).

Non si dovrebbe mai implementare IDisposable in una classe a meno che non ne hai bisogno. Per essere molto specifico, ci sono circa 5 volte in cui si sarebbe mai bisogno / dovrebbe implementare IDisposable:

  1. La classe contiene in modo esplicito (cioè non tramite l'ereditarietà) tutte le risorse gestite che implementano IDisposable e devono essere puliti una volta che la classe non è più utilizzato. Ad esempio, se la classe contiene un'istanza di un ruscello, DbCommand, DataTable, ecc.

  2. La classe contiene esplicitamente tutte le risorse gestite che attuano un metodo Close () - per esempio IDataReader, IDbConnection, ecc Si noti che alcune di queste classi implementano IDisposable avendo Dispose () come pure un metodo Close ().

  3. La classe contiene esplicitamente una risorsa non gestita - per esempio un oggetto COM, puntatori (sì, è possibile utilizzare i puntatori in C #, ma devono essere dichiarate in blocchi 'non sicuri', etc. Nel caso di risorse non gestite, si dovrebbe anche fare in modo di chiamare System.Runtime.InteropServices.Marshal.ReleaseComObject () sul RCW. Anche se il RCW è, in teoria, un wrapper gestito, c'è ancora riferimento contando in corso sotto le coperte.

  4. Se la classe sottoscrive gli eventi utilizzando riferimenti forti. È necessario annullare la registrazione / staccarsi dagli eventi. Sempre per assicurarsi che questi non sono nulli prima di cercare di annullare la registrazione / li staccare!.

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

Un'alternativa consiglia di lavorare con gli oggetti COM e di dover utilizzare Marshal.ReleaseComObject () è quello di utilizzare la classe System.Runtime.InteropServices.SafeHandle.

Il BCL (Base Class squadra Library) ha un buon post su di esso qui http://blogs.msdn.com/bclteam/archive/2005/03/16/396900.aspx

Una nota molto importante da fare è che se si sta lavorando con WCF e per la bonifica delle risorse, si dovrebbe QUASI SEMPRE evitare il blocco 'usando'. Ci sono un sacco di post di blog là fuori e un po 'su MSDN sul perché questa è una cattiva idea. Ho anche inviato circa qui - non utilizzare 'utilizzando ()' con un proxy WCF

Utilizzando lambda invece di IDisposable.

Non sono mai stato entusiasta con il tutto utilizzando / idea IDisposable. Il problema è che richiede al chiamante di:

  • sanno che devono usare IDisposable
  • ricordarsi di utilizzare 'usando'.

Il mio nuovo metodo preferito è quello di utilizzare un metodo di fabbrica e un lambda invece

Immaginate che voglio fare qualcosa con uno SqlConnection (qualcosa che dovrebbe essere avvolto in un utilizzando). Classicamente si farebbe

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

Nuovo modo

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

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

DoWithConnection assomiglia a questo

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

MakeConnection è ora privato

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 "using".La domanda che vorrei porre è: perché dovrebbero farlo?La maggior parte degli sviluppatori non utilizzerà il termine "using" a meno che non sappiano che devono farlo e come fanno a saperlo.O

  • è ovvio che li abbiano 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 ad alcuni) che questa classe racchiude qualcosa che deve essere rilasciato.Utilizzeranno "using", ma ci sono altri casi in cui l'utilizzo non è possibile (l'ambito dell'oggetto non è locale);e negli altri casi dovranno iniziare a preoccuparsi della durata degli oggetti: io mi preoccuperei di sicuro.Ma questo non è necessario

Implementi Idisposable per consentire loro di utilizzare using, ma non utilizzeranno using a meno che tu non glielo dica.

Quindi non farlo

  1. Se state usando altri oggetti gestiti che utilizzano risorse non gestite, non è responsabilità dell'utente assicurarsi quelli sono finalizzati. La vostra responsabilità è quella di chiamare Dispose su quegli oggetti quando Dispose viene chiamato l'oggetto, e si ferma lì.

  2. Se la classe non utilizza risorse scarse, non riesco a capire il motivo per cui si dovrebbe fare la classe implementa IDisposable. Si dovrebbe farlo solo se sei:

    • Conoscere si avrà scarse risorse a oggetti presto, non solo ora (e voglio dire che come in "siamo ancora in via di sviluppo, che sarà qui prima che abbiamo finito", non come in "Penso che' ll bisogno di questo ")
    • Utilizzo di risorse scarse
  3. Sì, il codice che utilizza il codice deve chiamare il metodo Dispose del vostro oggetto. E sì, il codice che utilizza l'oggetto può utilizzare using come hai mostrato.

  4. (2 ancora?) E 'probabile che il WebClient utilizza sia le risorse non gestite o altre risorse gestite che implementano IDisposable. Il motivo esatto, tuttavia, non è importante. Ciò che è importante è che si implementa IDisposable, e così cade su di voi per agire su quella conoscenza disponendo dell'oggetto quando hai finito con esso, anche se si scopre WebClient non usa altre risorse a tutti.

Alcuni aspetti della un'altra risposta leggermente errato per 2 motivi:

In primo luogo,

using(NoGateway objNoGateway = new NoGateway())

in realtà è equivalente a:

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

Questo può sembrare ridicolo, dal momento che l'operatore 'nuovo' non dovrebbe mai tornare 'null' a meno che non si dispone di un'eccezione OutOfMemory. Ma considerare i seguenti casi: 1. Si chiama un 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 realizzazione - ricordare che ho visto il modello IDisposable implementato in modo errato per molte volte a molti clienti in cui gli sviluppatori è sufficiente aggiungere un metodo Dispose () senza che eredita da IDisposable ( male male male). Si potrebbe anche avere il caso di una risorsa IDisposable viene restituito da una proprietà o un metodo (di nuovo male, male, male - non 'dare via le vostre risorse IDisposable)

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

Se il 'come' operatore restituisce nulla (o proprietà o il metodo di ritorno la risorsa), e il codice nel 'utilizzando' blocco protegge da 'null', il codice non farà saltare in aria quando si cerca di chiamare Dispose su un null oggetto a causa del controllo nullo 'incorporato'.

La seconda ragione per la vostra risposta non è preciso è per i seguenti motivi stmt:

  

Un finalizzatore è chiamato il GC distruggere l'oggetto

In primo luogo, finalizzazione (così come GC stesso) è non-deterministico. Il CLR determina quando sarà chiamare un finalizzatore. vale a dire lo sviluppatore / code non ha idea. Se il pattern IDisposable è implementato correttamente (come ho postato sopra) e GC.SuppressFinalize () è stato chiamato, il Finalizer NON verrà chiamato. Questo è uno dei grandi motivi per implementare correttamente il modello in modo corretto. Poiché c'è solo 1 filo Finalizer per processo gestito, indipendentemente dal numero di processori logici, si può facilmente degradare le prestazioni backup o anche appeso il filo Finalizer dimenticando di chiamare GC.SuppressFinalize ().

ho postato una corretta attuazione del pattern Dispose sul mio blog: Come implementare correttamente il pattern Dispose

Smaltimento 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();
        }
    }
}
using(NoGateway objNoGateway = new NoGateway())

è equivalente a

try
{
    NoGateway = new NoGateway();
}

finally
{
    NoGateway.Dispose();
}

Un finalizzatore è chiamato il GC distruggere l'oggetto. Questo può essere in un momento del tutto diverso rispetto a quando si lascia il metodo. La Smaltire IDisposable è chiamato subito dopo aver lasciato il blocco utilizzando. Quindi il modello è di solito di utilizzare di utilizzare per le risorse liberi subito dopo che non è necessario più di loro.

1) WebClient è un tipo gestito, in modo da non avete bisogno di un finalizzatore. Il finalizzatore è necessaria nel caso gli utenti non Dispose () della classe NoGateway e il tipo nativo (che non sono raccolte dal GC) ha bisogno di essere ripulito dopo. In questo caso, se l'utente non chiama Dispose (), il WebClient contenuta sarà disposto dal GC subito dopo il NoGateway fa.

2) Indirettamente sì, ma si dovrebbe non devono preoccuparsi a questo proposito. Il vostro codice è corretto come stand e non è possibile impedire agli utenti di dimenticare di Dispose () molto facilmente.

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);
         }
      }
   }
}

Da quello che so, è altamente consigliato di non utilizzare il Finalizer / Destructor:

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

Per lo più, questo è dovuto al non sapere quando o se sarà chiamato. Il metodo dispose è molto meglio, soprattutto se ci utilizzare o vendere direttamente.

utilizzando è buono. usarlo:)

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