416 Stimmen

Verwendung der Finalize/Dispose-Methode in C#

C# 2008

Ich arbeite jetzt schon eine Weile daran und bin immer noch verwirrt über die Verwendung von Finalize- und Dispose-Methoden im Code. Meine Fragen sind unten:

  1. Ich weiß, dass wir nur einen Finalizer benötigen, wenn wir nicht verwaltete Ressourcen entsorgen. Wenn es jedoch verwaltete Ressourcen gibt, die Aufrufe an nicht verwaltete Ressourcen tätigen, müsste dann immer noch ein Finalizer implementiert werden?

  2. Wenn ich jedoch eine Klasse entwickle, die keine nicht verwaltete Ressource - direkt oder indirekt - verwendet, sollte ich die IDisposable damit die Clients dieser Klasse die "using-Anweisung" verwenden können?

    Wäre es machbar, IDisposable zu implementieren, nur um den Clients Ihrer Klasse die Verwendung der using-Anweisung zu ermöglichen?

    using(myClass objClass = new myClass())
    {
        // Do stuff here
    }
  3. Ich habe den folgenden einfachen Code entwickelt, um die Verwendung von Finalize/Dispose zu demonstrieren:

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

Frage zum Quellcode:

  1. Hier habe ich den Finalizer nicht hinzugefügt, und normalerweise wird der Finalizer vom GC aufgerufen, und der Finalizer ruft den Dispose auf. Da ich den Finalizer nicht habe, wann rufe ich die Dispose-Methode auf? Ist es der Client der Klasse, der sie aufrufen muss?

    Meine Klasse im Beispiel heißt also NoGateway und der Client könnte die Klasse wie folgt verwenden und entsorgen:

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

    Wird die Dispose-Methode automatisch aufgerufen, wenn die Ausführung das Ende des using-Blocks erreicht, oder muss der Client die Dispose-Methode manuell aufrufen? d.h.

    NoGateway objNoGateway = new NoGateway();
    // Do stuff with object
    objNoGateway.Dispose(); // finished with it
  2. Ich verwende die WebClient Klasse in meinem NoGateway Klasse. Denn WebClient implementiert die IDisposable Schnittstelle, bedeutet dies, dass WebClient indirekt nicht verwaltete Ressourcen verwendet? Gibt es eine feste Regel, um dies zu befolgen? Woher weiß ich, dass eine Klasse nicht verwaltete Ressourcen verwendet?

455voto

thecoop Punkte 43958

Das empfohlene IDisposable-Muster ist aquí . Bei der Programmierung einer Klasse, die IDisposable verwendet, sollten Sie im Allgemeinen zwei Muster verwenden:

Bei der Implementierung einer versiegelten Klasse, die keine unverwalteten Ressourcen verwendet, implementieren Sie einfach eine Dispose-Methode wie bei normalen Schnittstellenimplementierungen:

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

Wenn Sie eine nicht versiegelte Klasse implementieren, gehen Sie folgendermaßen vor:

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

Beachten Sie, dass ich keinen Finalizer in B Sie sollten nur dann einen Finalizer implementieren, wenn Sie tatsächlich nicht verwaltete Ressourcen zu entsorgen haben. Die CLR behandelt finalisierbare Objekte anders als nicht-finalisierbare Objekte, auch wenn SuppressFinalize genannt wird.

Sie sollten also keinen Finalizer deklarieren, es sei denn, Sie müssen es, aber Sie geben den Erben Ihrer Klasse einen Haken, um Ihre Dispose und selbst einen Finalizer implementieren, wenn sie nicht verwaltete Ressourcen direkt verwenden:

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

Wenn Sie nicht direkt unverwaltete Ressourcen verwenden ( SafeHandle und Freunde zählen nicht, da sie ihre eigenen Finalizer deklarieren), dann implementieren Sie keinen Finalizer, da die GC mit finalisierbaren Klassen anders umgeht, selbst wenn Sie den Finalizer später unterdrücken. Beachten Sie auch, dass, auch wenn B keinen Finalizer hat, ruft er trotzdem SuppressFinalize um alle Unterklassen, die einen Finalizer implementieren, korrekt zu behandeln.

Wenn eine Klasse die Schnittstelle IDisposable implementiert, bedeutet dies, dass es irgendwo einige nicht verwaltete Ressourcen gibt, die man loswerden sollte, wenn man die Klasse nicht mehr verwendet. Die eigentlichen Ressourcen sind in den Klassen gekapselt; Sie brauchen sie nicht explizit zu löschen. Ein einfacher Aufruf von Dispose() oder die Klasse in eine Hülle aus using(...) {} stellt sicher, dass alle nicht verwalteten Ressourcen bei Bedarf beseitigt werden.

126voto

Jordão Punkte 53117

Das offizielle Muster zur Implementierung IDisposable ist schwer zu verstehen. Ich glaube, das hier ist besser :

public class BetterDisposableClass : IDisposable {

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

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

  ~BetterDisposableClass() {
    CleanUpNativeResources();
  }

}

Eine noch besser Die Lösung besteht darin, eine Regel aufzustellen, die Sie siempre müssen Sie eine Wrapper-Klasse für jede nicht verwaltete Ressource erstellen, die Sie behandeln müssen:

public class NativeDisposable : IDisposable {

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

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

  ~NativeDisposable() {
    CleanUpNativeResource();
  }

}

Mit SafeHandle und ihre Derivate, sollten diese Klassen sehr selten .

Das Ergebnis für Wegwerfklassen, die nicht direkt mit nicht verwalteten Ressourcen zu tun haben, ist selbst bei vorhandener Vererbung sehr überzeugend: sie müssen sich nicht mehr mit nicht verwalteten Ressourcen befassen . Sie werden einfach umzusetzen und zu verstehen:

public class ManagedDisposable : IDisposable {

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

}

41voto

Dave Black Punkte 6469

Beachten Sie, dass jede IDisposable-Implementierung dem folgenden Muster folgen sollte (IMHO). Ich habe dieses Muster auf der Grundlage von Informationen von mehreren ausgezeichneten .NET-"Göttern" entwickelt, den .NET Framework Entwurfsrichtlinien (Beachten Sie, dass MSDN sich aus irgendeinem Grund nicht daran hält!). Die .NET Framework Design Guidelines wurden von Krzysztof Cwalina (CLR Architect zu der Zeit) und Brad Abrams (ich glaube, der CLR Program Manager zu der Zeit) und Bill Wagner ([Effective C#] und [More Effective C#] (schauen Sie einfach nach diesen auf Amazon.com:

Beachten Sie, dass Sie NIEMALS einen Finalizer implementieren sollten, es sei denn, Ihre Klasse enthält direkt (nicht geerbt) UNverwaltete Ressourcen. Sobald Sie einen Finalizer in einer Klasse implementiert haben, wird er garantiert für eine zusätzliche Sammlung leben, selbst wenn er nie aufgerufen wird. Er wird automatisch in die Finalisierungswarteschlange gestellt (die auf einem einzigen Thread läuft). Ein sehr wichtiger Hinweis: Der gesamte Code, der innerhalb eines Finalizers ausgeführt wird (sollten Sie einen implementieren müssen), MUSS thread-sicher UND ausnahmesicher sein! Andernfalls passieren SCHLECHTE Dinge...(d.h. unbestimmtes Verhalten und im Falle einer Ausnahme ein fataler, nicht wiederherstellbarer Absturz der Anwendung).

Das Muster, das ich zusammengestellt (und ein Codeschnipsel dafür geschrieben) habe, lautet wie folgt:

#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

Hier ist der Code für die Implementierung von IDisposable in einer abgeleiteten Klasse. Beachten Sie, dass Sie die Vererbung von IDisposable nicht explizit in der Definition der abgeleiteten Klasse aufführen müssen.

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

Ich habe diese Umsetzung in meinem Blog veröffentlicht unter: Ordnungsgemäße Implementierung des Dispose-Musters

23voto

Dave Black Punkte 6469

Ich stimme zu mit pm100 (ich hätte dies in meinem früheren Beitrag ausdrücklich sagen sollen).

Sie sollten nie IDisposable in einer Klasse implementieren, wenn Sie es nicht brauchen. Um ganz genau zu sein, gibt es etwa 5 Fälle, in denen Sie IDisposable implementieren müssen/sollten:

  1. Ihre Klasse enthält explizit (d.h. nicht über Vererbung) alle verwalteten Ressourcen, die IDisposable implementieren und aufgeräumt werden sollten, sobald Ihre Klasse nicht mehr verwendet wird. Wenn Ihre Klasse zum Beispiel eine Instanz eines Streams, DbCommand, DataTable usw. enthält.

  2. Ihre Klasse enthält explizit alle verwalteten Ressourcen, die eine Close()-Methode implementieren - z.B. IDataReader, IDbConnection, etc. Beachten Sie, dass einige dieser Klassen IDisposable implementieren, indem sie sowohl über Dispose() als auch über eine Close()-Methode verfügen.

  3. Ihre Klasse enthält explizit eine nicht verwaltete Ressource - z. B. ein COM-Objekt, Zeiger (ja, Sie können Zeiger in verwaltetem C# verwenden, aber sie müssen in 'unsicheren' Blöcken deklariert werden, usw.). Im Falle von nicht verwalteten Ressourcen sollten Sie auch sicherstellen, dass Sie System.Runtime.InteropServices.Marshal.ReleaseComObject() auf dem RCW aufrufen. Auch wenn die RCW theoretisch ein verwalteter Wrapper ist, findet unter der Haube immer noch Referenzzählung statt.

  4. Wenn Ihre Klasse Ereignisse mit starken Referenzen abonniert. Sie müssen sich von den Ereignissen abmelden/abkoppeln. Stellen Sie immer sicher, dass diese nicht zuerst null sind, bevor Sie versuchen, sie abzumelden/zu trennen!

  5. Ihre Klasse enthält eine beliebige Kombination der oben genannten Punkte...

Eine empfohlene Alternative zur Arbeit mit COM-Objekten und der Verwendung von Marshal.ReleaseComObject() ist die Verwendung der Klasse System.Runtime.InteropServices.SafeHandle.

Das BCL (Base Class Library Team) hat hier einen guten Blogbeitrag darüber veröffentlicht http://blogs.msdn.com/bclteam/archive/2005/03/16/396900.aspx

Eine sehr wichtige Anmerkung ist, dass Sie, wenn Sie mit WCF arbeiten und Ressourcen bereinigen, IMMER den "using"-Block vermeiden sollten. Es gibt viele Blogbeiträge und einige auf MSDN darüber, warum dies eine schlechte Idee ist. Ich habe auch hier darüber geschrieben - Verwenden Sie nicht 'using()' mit einem WCF-Proxy

13voto

pm100 Punkte 40532

Verwendung von Lambdas anstelle von IDisposable.

Ich war nie begeistert von der Idee der Verwendung von Einwegartikeln. Das Problem ist, dass es den Aufrufer zu erfordert:

  • wissen, dass sie IDisposable verwenden müssen
  • vergessen Sie nicht, "mit" zu verwenden.

Meine neue bevorzugte Methode ist es, stattdessen eine Fabrikmethode und ein Lambda zu verwenden

Stellen Sie sich vor, ich möchte etwas mit einer SqlConnection tun (etwas, das in einem using verpackt werden sollte). Klassischerweise würde man tun

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

Neuer Weg

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

Im ersten Fall könnte der Aufrufer einfach die using-Syntax nicht verwenden. Im zweiten Fall hat der Benutzer keine Wahl. Es gibt keine Methode, die ein SqlConnection-Objekt erstellt, der Aufrufer muss DoWithConnection aufrufen.

DoWithConnection sieht wie folgt aus

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

MakeConnection ist jetzt privat

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X