Laden...

Dispose implementieren und verwenden (IDisposable)

Erstellt von herbivore vor 16 Jahren Letzter Beitrag vor 15 Jahren 34.200 Views
herbivore Themenstarter:in
49.485 Beiträge seit 2005
vor 16 Jahren
Dispose implementieren und verwenden (IDisposable)

Beschreibung:

Vielleicht nützt euch die Implementierung, die ich in meinen Klassen verwende.

//#######################################################################
// Close, IDisposable.Dispose, Dispose (bool) und Destruktor (= Finalize)
//
// Die eigentliche Logik findet in Dispose (bool) statt. Deshalb ist
// Dispose (bool) auch virtual. Unterklassen können hier die
// Funktionalität der Objektzerstörung erweitern.
//
// Alle anderen Methoden rufen nur direkt oder indirekt Dispose (bool)
// auf. Deshalb sind sie absichtlich nicht virtual.
//
// - Close ist für die Verwendung durch den Benutzer der Klasse gedacht.
//
// - IDisposable.Dispose dagegen die Implementierung von der IDisposable
//   Schnittstelle gedacht und wird z.B. bei Verwendung der
//   using-Anweisung aufgerufen.
//   (Man sieht, dass in diesem Fall Close umgangen wird, aber deshalb
//   ist Close ja auch nicht virtual.)
//
// - Der Destruktor ist für den Aufruf durch den GC gedacht.
//
// Durch den Parameter von Dispose (bool) kann man unterscheiden, ob
// Dispose (bool) vom Benutzer oder vom Finalizer aufgerufen wurde. Da beim
// Aufruf per Finalizer für die enthaltenen Objekte schon deren Finalizer auf-
// gerufen sein kann, darf man auf Objekte mit Finalizer nicht mehr zugreifen.
//
// Optional: Wenn die Bezeichnung 'Close' für das Beenden der Lebensdauer
// von Objekten einer Klasse unzutreffend oder irreführend wäre, kann
// man auch den Namen 'Close' durch 'Dispose' ersetzen, ohne die
// Implementierung zu ändern. Die andere Option für diesen Fall wäre,
// die Close-Methode zu entfernen und 'IDisposable.Dispose' in 'Dispose'
// umzubenennen.
//
//#######################################################################

//=======================================================================
public void Close ()
{
   ((IDisposable)this).Dispose ();
}

//=======================================================================
void IDisposable.Dispose ()
{
   Dispose (true);
   GC.SuppressFinalize (this);
}

//=======================================================================
~MyClass ()
{
   Dispose (false);
}

//=======================================================================
protected virtual void Dispose (bool fDisposing)
{
   if (fDisposing) {
      // Hier die verwalteten Ressourcen freigeben
   }
   // Hier die unverwalteten Ressourcen freigeben
}

Schlagwörter: Disposable, IDisposable, [Disposeable, IDisposeable], Dispose, Close, Destruktor, Finalize, Finalizer, implementieren, GC, Garbage Collector, Garbage Collection, zerstören, aufräumen, abräumen, wegräumen, Best Practice, Entwurfsmuster, Design Pattern, Code Snippet, 1000 Worte

Quelle: myCSharp.de

PS: Weitere wichtige Informationen gibt es in den folgenden Beiträgen in diesem Thread hier sowie in
Dispose für jede Klasse implementieren?
Dispose: sollten Referenzen explizit auf null gesetzt werden?
Wie kann sichergestellt werden, dass Dispose auch bei Fehlern aufgerufen wird? [==> using]
MemoryLeaks wegen nicht abgehängter Event-Handler per Dispose vermeiden
MemoryLeaks durch nicht abgehängte Events
Detaillierte Untersuchung von IDisposable
CA1063: IDisposable korrekt implementieren
DG Update: Dispose, Finalization, and Resource Management (sehr detailliert und ausführlich)

PPS: Dieser Thread ist (Stand 2012) immer noch aktuell und wird es aller Voraussicht nach auch in Zukunft bleiben.

J
9 Beiträge seit 2007
vor 16 Jahren

Hallo ich wollte mal nach fragen, wie ich genau in folgender Methode die Resourcen freigebe ??
Überall wo ich bis jetzt geschaut habe, steht an dieser Stelle immer nur ein KOmmentar, aber nie ein konkrtes Beispiel.

Vielleicht bin ich ja der einzige mit diesem Problem das ich es grade an der stelle nicht raffe, aber ich wäre dankbar, wenn dies noch mal jemand erläutern könne.

Gruß und danke

JHK

//=======================================================================  
protected virtual void Dispose (bool fDisposing)  
{  
   if (fDisposing) {  
      // Hier die verwalteten Ressourcen freigeben  
   }  
   // Hier die unverwalteten Ressourcen freigeben  
}  
herbivore Themenstarter:in
49.485 Beiträge seit 2005
vor 16 Jahren

Hallo jhk,

ich wollte mal nach fragen, wie ich genau in folgender Methode die Resourcen freigebe ??

bei verwalteten Ressourcen (also normale-.NET Objekte), die IDisposable implementieren, indem du Dispose aufrufst.

Bei verwalteten Ressourcen, die IDisposable nicht implementieren, musst du normalerweise gar nichts machen.

Und zu unverwalteten Ressourcen kann man nichts sagen, weil das da von der Ressource abhängt und immer unterschiedlich ist. Wenn du z.B. eine Datei per Win32-API geöffnet hättest, würdest du die Ressourcen freigeben (was einfach nur bedeutet, die Datei zu schließen), indem du die Win32-API-Funktion fürs Schließen der Datei aufrufst.

herbivore

herbivore Themenstarter:in
49.485 Beiträge seit 2005
vor 15 Jahren

Hallo zusammen,

einige Fragen im Zusammenhang mit der Benutzung von Dispose taucht immer wieder auf:

Wann sollte man IDisposable implementieren?

Siehe die Antwort an norman_timo in Dispose implementieren und verwenden (IDisposable).

Wann muss man auch den Destruktor/Finalizer implementieren?

Diesen braucht man nur zu implementieren, wenn die Objekte der Klasse direkt unverwaltete Ressourcen enhalten. Lässt man den Destruktor/Finalizer (und entsprechend auch das GC.SuppressFinalize) weg, weil dies nicht der Fall ist, sollte man den restlichen Aufbau des Design-Patterns unverändert beibehalten, für den Fall, dass in der Klasse oder einer ihrer Unterklassen später doch noch unverwaltete Ressourcen benutzt werden, damit der Änderungsaufwand so gering wie möglich ausfällt.

Wann soll man bzw. wann muss man Dispose aufrufen?

Wenn ein Objekt eine Dispose()-Methode anbietet und man das Objekt selbst erzeugt hat, dann sollte bzw. muss man auch Dispose aufrufen, sobald das Objekt nicht mehr benötigt wird.

Umgekehrt gilt, dass man für Objekte, die man nicht selbst erzeugt hat (z.B. das Graphics-Objekt, das man in den PaintEventArgs von OnPaint übergeben bekommt), Dispose nicht aufrufen darf.

Und natürlich darf man für ein selbst erzeugtes Objekt Dispose dann nicht aufrufen, wenn es noch an anderen Stellen benötigt wird.

So weit die Grundregeln. Allerdings kann im Einzelfall auch etwas anders festgelegt werden oder sein. Die Dokumentation einer Methode kann also durchaus bestimmen, dass die Methode ein Objekt, das der Aufrufer erzeugt hat und das sie per Parameter bekommt, selbst per Dispose zerstört. Eine solche abweichende Regelung ist insbesondere dann sinnvoll, wenn ein Objekt von einem Programmteil endgültig in die Zuständigkeit eines anderen Programmteils übergeben wird. Zum Beispiel schließt ein StreamReader beim Close/Dispose automatisch seinen BaseStream, egal wer diesen erzeugt hat.

Was passiert, wenn man vergisst, Dispose aufzurufen?

Wenn Dispose richtig implementiert ist (und davon sollte man bis zum Beweis des Gegenteils immer ausgehen), also wenn so wie oben angegeben Dispose durch den Destruktor aufgerufen wird, wird Dispose automatisch durch den GC aufgerufen (weil der GC den Destruktor aufruft), wenn das Objekt durch den GC freigegeben wird.

Wenn der GC sowieso automatisch Dispose aufruft, warum sollte/muss man es dann selber machen?

Der GC gibt nicht mehr benötigte Objekte erst frei, wenn er es für richtig hält, also z.B. erst wenn der Hauptspeicher anderweitig benötigt wird. Es kann also sein, dass es sehr lange dauert, bis der CG diese Objekte freigibt. Objekte, die Dispose anbieten, enthalten aber in der Regel unverwaltete Ressourcen, für die oft spezielle Speicherbereiche verwendet werden, die viel schneller zur Neige gehen als der Hauptspeicher. Durch das manuelle Aufrufen von Dispose kann man diese Situation vermeiden.

Kann man sich davor schützen, dass man das Aufrufen von Dispose vergisst oder Dispose aus anderen Gründen (z.B. durch eine unerwartete Exception) nicht aufgerufen wird?

Ja, wenn das Objekt innerhalb einer Methode erzeugt wird und spätestens am Ende der Methode nicht mehr benötigt wird. In diesen Fällen, kann man using verwenden. Hier sagt Code wieder mehr als 1000 Worte:


using (StreamReader sr = new StreamReader ("tmp.cs")) {

   // hier kann der StreamReader sr verwendet werden

} // <= hier wird automatisch sr.Dispose aufgerufen und zwar
  //    selbst dann, wenn in dem Block eine Exception auftritt.
  //    Dabei hat sr.Dispose denselben Effekt wie sr.Close.

**
Wird durch Dispose der Speicher für das Objekts selbst freigegeben?**

Nein! Das muss man klar trennen. Der Speicher für das Objekts selbst wird ganz normal durch den GC freigegeben. Also erst wenn dieser erkennt, dass das Objekt nicht mehr benötigt wird und auch erst, wenn der GC es für nötigt hält, also oft erst, wenn der Speicher anderweitig benötigt wird. Das manuelle Aufrufen von Dispose für ein Objekt beeinflusst in keiner Weise die Freigabe des Speichers für des Objekts selbst. Ein solches Objekt kann also, wenn es weiterhin referenziert wird, weiterbestehen, bis das Programm terminiert.

herbivore

4.207 Beiträge seit 2003
vor 15 Jahren

Hallo,

eine Sache fehlt da meiner Meinung nach aber noch: Die Dispose-Methode muss eine Variable auf true setzen, dass das Objekt disposed wurde.

Auf diese Variable müssen dann alle anderen Methoden testen und gegebenenfalls eine ObjectDisposedException auslösen, um zu verhindern, dass ein Objekt nach dem Disposen noch verwendet werden kann.

Viele Grüße,

Golo

Hinweis von herbivore vor 15 Jahren

Siehe dazu auch meine Antwort weiter unten.

Wissensvermittler und Technologieberater
für .NET, Codequalität und agile Methoden

www.goloroden.de
www.des-eisbaeren-blog.de

4.506 Beiträge seit 2004
vor 15 Jahren

@Herbivore:

vielleicht kann man Dein Snippet noch mit einer Richtlinie erweitern wann man IDisposable in eigenen Klassen verwendet? Für mich gilt die Richtlinie, dass ich das nur bei unmanaged Resourcen verwende, aber vielleicht gibt es noch andere Sinnvolle Anwendungsgebiete?

Grüße
Norman-Timo

Hinweis von herbivore vor 15 Jahren

Siehe dazu auch meine Antwort weiter unten.

A: “Wie ist denn das Wetter bei euch?”
B: “Caps Lock.”
A: “Hä?”
B: “Na ja, Shift ohne Ende!”

3.971 Beiträge seit 2006
vor 15 Jahren

Weiteres Anwendungsgebiet wäre zum Beispiel das "Schließen" (Close) von Instanzen, um die Klasse mit Using benutzen zu können. Gute Beispiele wären da IDataReader und die Stream-Klassen.

Es gibt 3 Arten von Menschen, die die bis 3 zählen können und die, die es nicht können...

O
778 Beiträge seit 2007
vor 15 Jahren

Streams sind nicht zwingend unmanaged Ressourcen (System.IO.MemoryStream 🙂). Using ist ja nun aber kein Selbstzweck. Man wuerde ja nicht IDisposable implementieren, damit man using einsetzen kann, sondern using einsetzen, um sauber mit Klassen umzugehen, die IDisposable implementieren.

herbivore Themenstarter:in
49.485 Beiträge seit 2005
vor 15 Jahren

Hallo Golo Roden,

Die Dispose-Methode muss eine Variable auf true setzen, dass das Objekt disposed wurde.

eine extra Variable braucht man dazu in der Regel nicht, weil man typischerweise an anderen Variablen oder allgemeiner gesprochen am Zustand des Objekts erkennen kann, ob Dispose aufgerufen wurde.

Auf diese Variable müssen dann alle anderen Methoden testen und gegebenenfalls eine ObjectDisposedException auslösen, um zu verhindern, dass ein Objekt nach dem Disposen noch verwendet werden kann.

Sicher ist, dass Methoden, die ohne die Ressourcen nicht sinnvoll arbeiten können, eine ObjectDisposedException werfen sollten. Aber ob die (öffentlichen) Instanzmethoden (außer Dispose) - und eigentlich auch die Properties - wirklich alle immer eine ObjectDisposedException auslösen sollten, wenn für das Objekt schon Dispose aufgerufen wurde, finde ich nicht so klar. Die Doku lässt durch das "u.U." einen gewissen Raum, das anders festzulegen.

Lösen Sie für diesen Typ eine ObjectDisposedException über Instanzmethoden (nicht Dispose) aus, wenn die Ressourcen bereits freigegeben wurden.

Es empfiehlt sich u. U. festzulegen, dass ein Objekt nicht mehr verwendet werden kann, nachdem seine Dispose-Methode aufgerufen wurde.

Ich kann mir Fälle vorstellen, in denen es sinnvoll ist, bestimmte Operationen auch dann zuzulassen, wenn das Objekt schon disposed ist, z.B. könnte man ein Windows.Form so implementieren, dass eine Abfrage seiner Ergebnisse auch dann noch möglich ist, wenn es schon geschlossen (also disposed) ist.

Hallo norman_timo,

vielleicht kann man Dein Snippet noch mit einer Richtlinie erweitern wann man IDisposable in eigenen Klassen verwendet?

ich denke, eine Klasse sollte IDisposable implementieren, wenn ihre Objekte ...*unverwaltete Ressourcen enthalten, *knappe Ressourcen binden, egal ob diese verwaltet oder unverwaltet sind, und/oder *Objekte enthalten, die selbst Dispose - oder Close oder eine vergleichbare Methode - anbieten

Das stimmt mit dem überein, was Rainbird in Wann sollte eine Klasse von IDisposable erben? schreibt.

Laut Doku gibt es noch einen vierten (allerdings wohl eher selten zutreffenden) Grund:

Implementieren Sie das Dispose-Entwurfsmuster für einen Basistyp, der normalerweise abgeleitete Typen aufweist, die Ressourcen verwenden, auch wenn der Basistyp keine Ressourcen verwendet. Wenn der Basistyp über eine Close-Methode verfügt, weist dies häufig darauf hin, dass eine Dispose-Methode implementiert werden muss. Implementieren Sie in solchen Fällen keine Finalize-Methode für den Basistyp. Finalize sollte für abgeleitete Typen implementiert werden, die Ressourcen einfügen, die eine Bereinigung erforderlich machen.

Eine weitere interessante Diskussion zu Dispose findet sich in verwaltet - unverwaltet .

herbivore

3.971 Beiträge seit 2006
vor 15 Jahren

Es gibt 3 Arten von Menschen, die die bis 3 zählen können und die, die es nicht können...