Verwendung der Finalize / Dispose-Methode in C #

381

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 jedoch verwaltete Ressourcen vorhanden sind, die nicht verwaltete Ressourcen aufrufen, muss dann noch ein Finalizer implementiert werden?

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

    Wäre es möglich, IDisposable zu implementieren, damit Clients Ihrer Klasse die using-Anweisung verwenden können?

    using(myClass objClass = new myClass())
    {
        // Do stuff here
    }
  3. Ich habe diesen einfachen Code unten 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 Dispose auf. Wann rufe ich die Dispose-Methode auf, da ich den Finalizer nicht habe? Ist es der Client der Klasse, der es aufrufen muss?

    Meine Klasse im Beispiel heißt also NoGateway, und der Client kann die Klasse folgendermaßen verwenden und entsorgen:

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

    Würde die Dispose-Methode automatisch aufgerufen, wenn die Ausführung das Ende des using-Blocks erreicht, oder muss der Client die dispose-Methode manuell aufrufen? dh

    NoGateway objNoGateway = new NoGateway();
    // Do stuff with object
    objNoGateway.Dispose(); // finished with it
  2. Ich benutze die WebClientKlasse in meiner NoGatewayKlasse. Da WebClientdie Geräte - IDisposableSchnittstelle, bedeutet das , die WebClientindirekt von nicht verwalteten Ressourcen verwendet? Gibt es eine feste Regel, um dies zu befolgen? Woher weiß ich, dass eine Klasse nicht verwaltete Ressourcen verwendet?

ant2009
quelle
1
Ist dieses komplizierte Entwurfsmuster tatsächlich erforderlich, um dieses Problem der Ressourcenfreigabe zu lösen?
Zinking

Antworten:

422

Das empfohlene IDisposable-Muster finden Sie hier . Wenn Sie eine Klasse programmieren, die IDisposable verwendet, sollten Sie im Allgemeinen zwei Muster verwenden:

Wenn Sie eine versiegelte Klasse implementieren, die keine nicht verwalteten 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...
    }
}

Gehen Sie beim Implementieren einer nicht versiegelten Klasse 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 deklariert habe B. Sie sollten einen Finalizer nur implementieren, wenn Sie tatsächlich nicht verwaltete Ressourcen zur Verfügung haben. Die CLR behandelt finalisierbare Objekte anders als nicht finalisierbare Objekte, selbst wenn sie SuppressFinalizeaufgerufen wird.

Sie sollten also keinen Finalizer deklarieren, es sei denn, Sie müssen, aber Sie geben den Erben Ihrer Klasse einen Haken, um Ihren aufzurufen Disposeund einen Finalizer selbst zu 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 verwaltete Ressourcen nicht direkt verwenden ( SafeHandleund Freunde nicht zählen, da sie ihre eigenen Finalizer deklarieren), implementieren Sie keinen Finalizer, da der GC mit finalisierbaren Klassen anders umgeht, auch wenn Sie den Finalizer später unterdrücken. Beachten Sie auch, dass, obwohl Bes keinen Finalizer gibt, immer noch aufgerufen wird SuppressFinalize, alle Unterklassen, die einen Finalizer implementieren, korrekt zu behandeln.

Wenn eine Klasse die IDisposable-Schnittstelle implementiert, bedeutet dies, dass irgendwo nicht verwaltete Ressourcen vorhanden sind, die entfernt werden sollten, wenn Sie die Klasse nicht mehr verwenden. Die tatsächlichen Ressourcen sind in den Klassen gekapselt. Sie müssen sie nicht explizit löschen. Durch einfaches Aufrufen Dispose()oder Umschließen der Klasse in a using(...) {}wird sichergestellt, dass nicht verwaltete Ressourcen bei Bedarf entfernt werden.

thecoop
quelle
26
Ich stimme thecoop zu. Beachten Sie, dass Sie keinen Finalizer benötigen, wenn Sie nur mit verwalteten Ressourcen arbeiten (in der Tat sollten Sie NICHT versuchen, von Ihrem Finalizer aus auf verwaltete Objekte zuzugreifen (außer "dies"), da es keine garantierte Reihenfolge gibt, in der die GC bereinigt Objekte. Wenn Sie .Net 2.0 oder besser verwenden, können (und sollten) Sie SafeHandles verwenden, um nicht verwaltete Handles zu verpacken. Safehandles reduzieren die Notwendigkeit, Finalizer für Ihre verwalteten Klassen zu schreiben, erheblich. Blogs.msdn. com / bclteam / archive / 2005/03/16 / 396900.aspx
JMarsch
5
Ich denke, es ist besser, MessageBox.Show ("Fehler", + GetType (). Name + "nicht entsorgt") im Finalizer aufzurufen, da Einwegobjekte IMMER entsorgt werden sollten, und wenn Sie dies nicht tun, ist es Am besten so früh wie möglich darauf aufmerksam machen.
Erikkallen
95
@erikkallen ist das ein witz? :)
Alex Norcliffe
2
In der CLR ist zusätzlicher Rechenaufwand erforderlich, um Klassen mit aktiven Finalisierern zu verfolgen. - Die Implementierung eines Finalizers führt dazu. Das Aufrufen von GC.SuppressFinalize bedeutet, dass der Finalizer nicht zur Laufzeit aufgerufen werden sollte. Es geht trotzdem Gen2. Fügen Sie keinen Finalizer hinzu, wenn Sie nicht mit verwalteten Ressourcen arbeiten. Versiegelte oder nicht versiegelte Klassenmodifikatoren sind für diesen Punkt irrelevant.
Ritch Melton
3
@Ritch: Zitat? Das ist nicht unbedingt eine schlechte Sache; Wenn Sie implementieren IDisposable, besteht die Möglichkeit, dass es ohnehin eine Weile herumhängt. Sie sparen der CLR den Aufwand, sie von Gen0 -> Gen1 -> Gen2
thecoop
123

Das offizielle Muster IDisposableist schwer zu verstehen. Ich glaube, das 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 bessere Lösung besteht darin, eine Regel zu haben, nach der Sie immer eine Wrapper-Klasse für jede nicht verwaltete Ressource erstellen müssen, die Sie verarbeiten müssen:

public class NativeDisposable : IDisposable {

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

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

  ~NativeDisposable() {
    CleanUpNativeResource();
  }

}

Mit SafeHandleund seinen Derivaten sollten diese Klassen sehr selten sein .

Das Ergebnis für verfügbare Klassen, die sich selbst bei Vorhandensein von Vererbung nicht direkt mit nicht verwalteten Ressourcen befassen, ist leistungsstark: Sie müssen sich nicht mehr mit nicht verwalteten Ressourcen befassen . Sie sind einfach zu implementieren und zu verstehen:

public class ManagedDisposable : IDisposable {

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

}
Jordão
quelle
@ Kyle: Danke! Ich mag es auch :-) Es gibt eine ist folgen , um es bis hier .
Jordão
4
Obwohl eine Sache, die ich beachten möchte, ist, dass sie nicht verhindert, dass sie zum zweiten Mal aufgerufen wird.
HuseyinUslu
5
@HuseyinUslu: Dies ist nur die Essenz des Musters. Sie können natürlich eine disposedFlagge hinzufügen und entsprechend prüfen.
Jordão
2
@didibus: Es ist ganz einfach, ein disposedFlag hinzuzufügen , es vor dem Entsorgen zu überprüfen und nach dem Entsorgen zu setzen. Suchen Sie hier nach der Idee. Sie sollten das Flag auch vor allen Methoden der Klasse überprüfen. Macht Sinn? Ist es kompliziert?
Jordão
1
+1 für „Eine noch bessere Lösung ist es, eine Regel zu haben , dass Sie immer eine Wrapper - Klasse für jede nicht verwaltete Ressource erstellen müssen , die Sie behandeln müssen“ . Ich bin in einem Addon für VLC darauf gestoßen und benutze es seitdem. Spart so viele Kopfschmerzen ...
Franz B.
37

Beachten Sie, dass jede IDisposable-Implementierung dem folgenden Muster (IMHO) folgen sollte. Ich habe dieses Muster basierend auf Informationen von mehreren ausgezeichneten .NET "Göttern" der .NET Framework Design Guidelines entwickelt (beachten Sie, dass MSDN dies aus irgendeinem Grund nicht befolgt!). Die .NET Framework Design Guidelines wurden von Krzysztof Cwalina (damals CLR-Architekt) und Brad Abrams (ich glaube der damalige CLR-Programmmanager) sowie Bill Wagner ([Effective C #] und [More Effective C #]) geschrieben Suchen Sie nach diesen auf Amazon.com:

Beachten Sie, dass Sie NIEMALS einen Finalizer implementieren sollten, es sei denn, Ihre Klasse enthält direkt nicht verwaltete Ressourcen (erbt diese nicht). Sobald Sie einen Finalizer in einer Klasse implementieren, wird er garantiert für eine zusätzliche Sammlung leben, auch wenn er nie aufgerufen wird. Es wird automatisch in die Finalisierungswarteschlange gestellt (die auf einem einzelnen Thread ausgeführt wird). Ein sehr wichtiger Hinweis: Der gesamte Code, der in einem Finalizer ausgeführt wird (falls Sie einen implementieren müssen), MUSS threadsicher UND ausnahmesicher sein! SCHLECHTE Dinge passieren sonst ... (dh unbestimmtes Verhalten und im Falle einer Ausnahme ein schwerwiegender nicht behebbarer Anwendungsabsturz).

Das Muster, das ich zusammengestellt (und für das ich ein Code-Snippet 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 zum Implementieren von IDisposable in einer abgeleiteten Klasse. Beachten Sie, dass Sie die Vererbung von IDisposable in der Definition der abgeleiteten Klasse nicht explizit auflisten 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 Implementierung in meinem Blog veröffentlicht unter: So implementieren Sie das Entsorgungsmuster ordnungsgemäß

Dave Black
quelle
Kann jemand auch ein Muster für eine abgeleitete Klasse hinzufügen (abgeleitet von dieser Basisklasse)
akjoshi
3
@akjoshi - Ich habe das obige Muster aktualisiert, um den Code für eine abgeleitete Einwegklasse aufzunehmen. Beachten Sie auch, implementieren Sie NIEMALS einen Finalizer in einer abgeleiteten Klasse ...
Dave Black
3
Microsoft scheint es zu mögen, das "disposed" -Flag am Ende der disposed-Methode zu setzen, aber das scheint mir falsch zu sein. Redundante Aufrufe zu "Entsorgen" sollen nichts bewirken; Während man normalerweise nicht erwarten würde, dass Dispose rekursiv aufgerufen wird, könnten solche Dinge passieren, wenn man versucht, ein Objekt zu entsorgen, das durch eine Ausnahme, die während der Konstruktion oder einer anderen Operation aufgetreten ist, in einem ungültigen Zustand belassen wurde. Ich würde denken, dass die Verwendung eines Interlocked.ExchangeGanzzahl- IsDisposedFlags in der nicht-virtuellen Wrapper-Funktion sicherer wäre.
Supercat
@ DaveBlack: Was ist, wenn Ihre Basisklasse keine nicht verwalteten Ressourcen verwendet, Ihre abgeleitete Klasse jedoch? Müssen Sie dann den Finalizer in der abgeleiteten Klasse implementieren? Und wenn ja, woher wissen Sie, dass die Basisklasse sie nicht bereits implementiert hat, wenn Sie keinen Zugriff auf die Quelle haben?
Didier A.
@ DaveBlack "Ich habe dieses Muster basierend auf Informationen von mehreren ausgezeichneten .NET" Göttern "entwickelt." Wenn einer der Götter Jon Skeet war, werde ich Ihrem Rat folgen.
Elisabeth
23

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

Sie sollten IDisposable niemals in einer Klasse implementieren, es sei denn, Sie benötigen es. Um genau zu sein, gibt es ungefähr fünf Mal, wenn Sie IDisposable jemals benötigen / implementieren würden:

  1. Ihre Klasse enthält explizit (dh nicht über Vererbung) verwaltete Ressourcen, die IDisposable implementieren, und sollte bereinigt werden, sobald Ihre Klasse nicht mehr verwendet wird. Wenn Ihre Klasse beispielsweise eine Instanz eines Streams, eines DbCommand, einer DataTable usw. enthält.

  2. Ihre Klasse enthält explizit alle verwalteten Ressourcen, die eine Close () -Methode implementieren - z. B. IDataReader, IDbConnection usw. 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, diese müssen jedoch in "unsicheren" Blöcken usw. deklariert werden. Bei nicht verwalteten Ressourcen sollten Sie dies ebenfalls sicherstellen Rufen Sie System.Runtime.InteropServices.Marshal.ReleaseComObject () auf dem RCW auf. Obwohl das RCW theoretisch ein verwalteter Wrapper ist, wird unter den Deckblättern immer noch Referenzzählungen durchgeführt.

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

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

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

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

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

Dave Black
quelle
3
Ich glaube, es gibt einen fünften Fall: Wenn Ihre Klasse Ereignisse mit starken Referenzen abonniert, sollten Sie IDisposable implementieren und sich von den Ereignissen in der Dispose-Methode abmelden.
Didier A.
Hallo Didibus. Ja du hast Recht. Das habe ich vergessen. Ich habe meine Antwort geändert, um dies als Fall aufzunehmen. Vielen Dank.
Dave Black
Die MSDN-Dokumentation für das Entsorgungsmuster fügt einen weiteren Fall hinzu: "BEACHTEN SIE, dass das grundlegende Entsorgungsmuster für Klassen implementiert wird, die selbst keine nicht verwalteten Ressourcen oder verfügbaren Objekte enthalten, aber wahrscheinlich Untertypen haben, die dies tun. Ein gutes Beispiel hierfür ist das System.IO .Stream-Klasse. Obwohl es sich um eine abstrakte Basisklasse handelt, die keine Ressourcen enthält, tun dies die meisten ihrer Unterklassen und aus diesem Grund implementiert sie dieses Muster. "
Gonen I
12

Verwenden von Lambdas anstelle von IDisposable.

Ich war noch nie begeistert von der ganzen Idee mit / IDisposable. Das Problem ist, dass der Anrufer Folgendes tun muss:

  • wissen, dass sie IDisposable verwenden müssen
  • Denken Sie daran, 'using' zu verwenden.

Meine neue bevorzugte Methode ist die Verwendung einer Factory-Methode und eines Lambda

Stellen Sie sich vor, ich möchte etwas mit einer SqlConnection machen (etwas, das in eine using-Datei eingeschlossen werden sollte). Klassisch würden Sie tun

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

Neuer Weg

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

Im ersten Fall konnte der Aufrufer die using-Syntax einfach 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 so aus

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

MakeConnection ist jetzt privat

pm100
quelle
2
Das Einwickeln von Dingen in Lambdas kann ein guter Ansatz sein, hat aber Grenzen. Es ist nicht schlecht für Situationen, in denen tatsächlich alle Konsumenten einer Klasse einen "using" -Block verwenden würden, aber es würde Situationen nicht zulassen, in denen eine Methode ein IDisposable in einem Klassenfeld speichern würde (entweder direkt oder in so etwas wie einem Iterator) ).
Supercat
@supercat Sie können argumentieren, dass das Verbieten der Speicherung von Ressourcenfressern eine gute Sache ist. Das hier vorgeschlagene Kreditmodell zwingt Sie dazu, mit Ihrer Nutzung der Ressource
schlank zu sein
Es kann eine gute Sache sein, aber es kann auch einige sehr vernünftige Operationen sehr schwierig machen. Angenommen, ein Datenbanklesertyp stellt anstelle von IEnumerable <T> eine Methode bereit DoForAll(Action<T>) where T:IComparable<T>, die den angegebenen Delegaten für jeden Datensatz aufruft. Wie würde bei zwei solchen Objekten, die beide Daten in sortierter Reihenfolge zurückgeben, eines alle Elemente ausgeben, die in einer Sammlung vorhanden sind, das andere jedoch nicht? Wenn die Typen implementiert sind IEnumerable<T>, könnte man einen Zusammenführungsvorgang ausführen, aber das funktioniert nicht mit DoForAll.
Supercat
Die einzige Möglichkeit, zwei DoForAllSammlungen zusammenzuführen, ohne zuerst eine in ihrer Gesamtheit in eine andere Struktur kopieren zu müssen, besteht darin, zwei Threads zu verwenden, die eher ressourcenschonend wären, als nur ein paar IEnumerables zu verwenden und vorsichtig zu sein um sie freizulassen.
Supercat
-1: gute Antwort auf eine Frage, die nicht gestellt wurde. Dies wäre eine großartige Antwort auf "Wie kann ich den Verbrauch von IDisposable-Objekten vereinfachen"
John Saunders,
10

Niemand beantwortete die Frage, ob Sie IDisposable implementieren sollten, obwohl Sie es nicht benötigen.

Kurze Antwort: Nein

Lange Antwort:

Dies würde es einem Verbraucher Ihrer Klasse ermöglichen, "using" zu verwenden. Die Frage, die ich stellen würde, ist - warum sollten sie es tun? Die meisten Entwickler verwenden "using" nur, wenn sie wissen, dass sie es müssen - und woher wissen sie das? Entweder

  • seine obviuos die sie aus Erfahrung (eine Socket-Klasse zum Beispiel)
  • es ist dokumentiert
  • Sie sind vorsichtig und können sehen, dass die Klasse IDisposable implementiert

Wenn Sie IDisposable implementieren, teilen Sie den Entwicklern (zumindest einigen) mit, dass diese Klasse etwas abschließt, das freigegeben werden muss. Sie werden 'using' verwenden - aber es gibt andere Fälle, in denen using nicht möglich ist (der Umfang des Objekts ist nicht lokal). und in diesen anderen Fällen müssen sie sich Sorgen um die Lebensdauer der Objekte machen - ich würde mir sicher Sorgen machen. Dies ist jedoch nicht erforderlich

Sie implementieren Idisposable, damit sie using verwenden können, aber sie verwenden using nicht, es sei denn, Sie weisen sie an.

Also tu es nicht

pm100
quelle
1
Ich verstehe nicht, warum ein Entwickler nicht using / dispose für ein Objekt verwenden würde, das IDisposable implementiert (es sei denn, das Programm wird sowieso beendet).
Adrianm
1
Der Punkt ist, dass ein Entwickler alle Aufrufe schreiben müsste, um in allen Codepfaden zu entsorgen, die dazu führen, dass die Referenzierung aufgehoben wird. Wenn ich zum Beispiel eine Instanz in ein Wörterbuch einfüge und Einträge aus dem Wörterbuch lösche, muss ich dispose aufrufen. Es ist eine Menge Ärger, der in diesem Fall nicht benötigt wird - das Objekt muss nicht entsorgt werden
pm100
3
@ PM100 Re: Unnötig IDisposable Umsetzung - Es gibt detaillierte Artikel zu codeproject.com/KB/dotnet/idisposable.aspx , die einige seltene Fälle diskutiert , wenn Sie darüber nachdenken möchten (sehr selten, ich bin sicher). Kurz gesagt: Wenn Sie die Notwendigkeit von IDisposable in der Zukunft oder in einem abgeleiteten Objekt vorhersehen können, sollten Sie IDisposable als "No-Op" in Ihrer Basisklasse implementieren, um Probleme beim "Slicing" zu vermeiden, wenn einige abgeleitete Objekte dies erfordern Entsorgung und andere nicht.
Kevin P. Rice
4
  1. Wenn Sie andere verwaltete Objekte verwenden, die nicht verwaltete Ressourcen verwenden, liegt es nicht in Ihrer Verantwortung, sicherzustellen, dass diese abgeschlossen sind. Sie sind dafür verantwortlich, Dispose für diese Objekte aufzurufen, wenn Dispose für Ihr Objekt aufgerufen wird und dort anhält.

  2. Wenn Ihre Klasse keine knappen Ressourcen verwendet, kann ich nicht verstehen, warum Sie Ihre Klasse dazu bringen würden, IDisposable zu implementieren. Sie sollten dies nur tun, wenn Sie:

    • Wisse, dass du bald knappe Ressourcen in deinen Objekten haben wirst, nur nicht jetzt (und ich meine, wie in "Wir entwickeln uns noch, es wird hier sein, bevor wir fertig sind", nicht wie in "Ich denke, wir werden das brauchen." ")
    • Knappe Ressourcen nutzen
  3. Ja, der Code, der Ihren Code verwendet, muss die Dispose-Methode Ihres Objekts aufrufen. Und ja, der Code, der Ihr Objekt verwendet, kann usingwie gezeigt verwendet werden.

  4. (Wieder 2?) Es ist wahrscheinlich, dass der WebClient entweder nicht verwaltete Ressourcen oder andere verwaltete Ressourcen verwendet, die IDisposable implementieren. Der genaue Grund ist jedoch nicht wichtig. Wichtig ist, dass IDisposable implementiert wird. Daher müssen Sie auf dieses Wissen reagieren, indem Sie das Objekt entsorgen, wenn Sie damit fertig sind, auch wenn sich herausstellt, dass WebClient überhaupt keine anderen Ressourcen verwendet.

Lasse V. Karlsen
quelle
4

Entsorgungsmuster:

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() { }
}

Beispiel für die Vererbung:

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();
        }
    }
}
Andrei Krasutski
quelle
4

Einige Aspekte einer anderen Antwort sind aus zwei Gründen leicht falsch:

Zuerst,

using(NoGateway objNoGateway = new NoGateway())

ist eigentlich gleichbedeutend mit:

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

Dies mag lächerlich klingen, da der 'neue' Operator niemals 'null' zurückgeben sollte, es sei denn, Sie haben eine OutOfMemory-Ausnahme. Beachten Sie jedoch die folgenden Fälle: 1. Sie rufen eine FactoryClass auf, die eine IDisposable-Ressource zurückgibt, oder 2. Wenn Sie einen Typ haben, der je nach Implementierung möglicherweise von IDisposable erbt oder nicht, denken Sie daran, dass das IDisposable-Muster häufig falsch implementiert wurde Zeiten bei vielen Clients, bei denen Entwickler einfach eine Dispose () -Methode hinzufügen, ohne von IDisposable zu erben (schlecht, schlecht, schlecht). Es kann auch vorkommen, dass eine IDisposable-Ressource von einer Eigenschaft oder Methode zurückgegeben wird (wieder schlecht, schlecht, schlecht - geben Sie Ihre IDisposable-Ressourcen nicht weiter).

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

Wenn der Operator 'as' null zurückgibt (oder eine Eigenschaft oder Methode, die die Ressource zurückgibt) und Ihr Code im Block 'using' vor 'null' schützt, wird Ihr Code beim Versuch, Dispose für ein Null-Objekt aufzurufen, nicht in die Luft gesprengt die 'eingebaute' Nullprüfung.

Der zweite Grund, warum Ihre Antwort nicht korrekt ist, ist der folgende:

Ein Finalizer wird aufgerufen, wenn der GC Ihr Objekt zerstört

Erstens ist die Finalisierung (sowie die GC selbst) nicht deterministisch. Die CLR bestimmt, wann ein Finalizer aufgerufen wird. dh der Entwickler / Code hat keine Ahnung. Wenn das IDisposable-Muster korrekt implementiert ist (wie oben beschrieben) und GC.SuppressFinalize () aufgerufen wurde, wird der Finalizer NICHT aufgerufen. Dies ist einer der Hauptgründe für die korrekte Implementierung des Musters. Da es unabhängig von der Anzahl der logischen Prozessoren nur 1 Finalizer-Thread pro verwaltetem Prozess gibt, können Sie die Leistung leicht beeinträchtigen, indem Sie den Finalizer-Thread sichern oder sogar aufhängen, indem Sie vergessen, GC.SuppressFinalize () aufzurufen.

Ich habe eine korrekte Implementierung des Entsorgungsmusters in meinem Blog veröffentlicht: So implementieren Sie das Entsorgungsmuster ordnungsgemäß

Dave Black
quelle
2
Bist du sicher über das Schreiben NoGateway = new NoGateway();und NoGateway != null?
Cœur
1
Bezog sich dies auf stackoverflow.com/a/898856/3195477 ? Es gibt keine Antwort mit dem Namen 'Icey'
UuDdLrLrSs
@ DaveInCaz es sieht so aus, als wäre das richtig. Ich sehe 'Icey' nirgendwo, aber der Kontext meiner Antwort scheint auf die Antwort gerichtet zu sein, die Sie über Ihren obigen Link erhalten haben. Vielleicht hat er seinen Benutzernamen geändert?
Dave Black
@ DaveBlack cool, danke. Ich habe das gerade direkt in den Text bearbeitet.
UuDdLrLrSs
2

1) WebClient ist ein verwalteter Typ, sodass Sie keinen Finalizer benötigen. Der Finalizer wird für den Fall benötigt, dass Ihre Benutzer Ihre NoGateway-Klasse nicht entsorgen () und der native Typ (der nicht vom GC erfasst wird) anschließend bereinigt werden muss. In diesem Fall wird der enthaltene WebClient direkt nach dem NoGateway vom GC entsorgt, wenn der Benutzer Dispose () nicht aufruft.

2) Indirekt ja, aber Sie sollten sich keine Sorgen machen müssen. Ihr Code ist in der jetzigen Form korrekt und Sie können Ihre Benutzer nicht daran hindern, Dispose () ganz einfach zu vergessen.

Jesse C. Slicer
quelle
2

Muster von 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);
         }
      }
   }
}
devnull
quelle
1
using(NoGateway objNoGateway = new NoGateway())

ist äquivalent zu

try
{
    NoGateway = new NoGateway();
}

finally
{
    NoGateway.Dispose();
}

Ein Finalizer wird aufgerufen, wenn der GC Ihr Objekt zerstört. Dies kann zu einem völlig anderen Zeitpunkt sein als beim Verlassen Ihrer Methode. Die Option "IDisposable entsorgen" wird sofort aufgerufen, nachdem Sie den using-Block verlassen haben. Daher wird das Muster normalerweise verwendet, um Ressourcen sofort freizugeben, nachdem Sie sie nicht mehr benötigen.

Daniel Fabian
quelle
1
Ein Finalizer wird nicht aufgerufen, wenn der GC das Objekt zerstört. Wenn "Finalisieren" überschrieben wird, wird das Objekt , wenn es ansonsten zerstört worden wäre , in eine Warteschlange von Objekten gestellt, die finalisiert werden müssen, wodurch vorübergehend ein starker Verweis darauf erstellt und - zumindest vorübergehend - "wiederbelebt" wird.
Supercat
-5

Soweit ich weiß, wird dringend empfohlen, den Finalizer / Destructor NICHT zu verwenden:

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

Meistens liegt dies daran, dass man nicht weiß, wann oder WENN es aufgerufen wird. Die Entsorgungsmethode ist viel besser, insbesondere wenn Sie uns direkt verwenden oder entsorgen.

Verwenden ist gut. benutze es :)

Nic Wise
quelle
2
Sie sollten dem Link in der Antwort von thecoop folgen. Ja, die Verwendung von / Dispose ist besser, aber eine Disposable-Klasse sollte auf jeden Fall beides implementieren.
Henk Holterman
Interessanterweise sagen alle Dokumente, die ich von Microsoft gelesen habe - z. B. die Richtlinien für das Framework-Design -, dass NIEMALS ein Destruktor verwendet wird. Verwenden Sie immer IDisposable.
Nic Wise
5
Unterscheiden Sie einfach zwischen der Verwendung einer Klasse und dem Schreiben der Klasse, und lesen Sie sie erneut.
Henk Holterman
stackoverflow.com/questions/2605412/… kann helfen
Alex Nolasco