Dateistream in einem "Destructor" schliessen
-
Hallo,
Ich hab eine simple, kleine Klasse, in deren Konstruktor ein StreamWriter erzeugt wird und in einer anderen Methode benutzt wird um in eine Datei zu schreiben.
Sobald die Inztanz der Klasse zerstoert wird, soll der Stream ueber Close() wieder geschlossen werden.
In C# ist das etwas problematisch. Ich moechte keine Methode meiner Klasse manuell aufrufen, um den Stream zu schliessen, damit die Texte im Stream tatsaechlich in die Datei geschrieben werden. (Ich gehe davon aus, dass beim schliessen ein C++ aehnliches ostream::flush stattfindet.)
Hab folgenden Code in die Klasse geschrieben:
~MeineKlasse() { this.stream.Close(); }
Sobald das Programm beendet wird, erhalte ich eine Exception, die besagt, dass der Stream bereits geschlossen ist. Ok, aber wenn der Stream schon irgendwo geschlossen wird, warum ist die Datei dann leer? Nur wenn ich den Stream manuell schliesse, steht auch tatsaechlich was drin.
Bei dem Stream handelt es sich um ein:
TextWriter text = new StreamWriter("test.txt");Weiss jemand Rat?
-
Das geht so nicht, C# != C++. C# benutzt eine Garbage Collection um Objekte abzubauen die nicht mehr gebraucht werden indem ein Finalize aufgerufen wird. Da Du nicht bestimmen kannst wann und in welcher Reihenfolge die GC Objekte abbaut ist es in Deinem Beispiel sehr wahrscheinlich, daß die GC das Stream-Objekt bereits finalized hat bevor Deine Klasse an die Reihe kommt. (Es werden wohl erst die Objekte einer Klasse finalized bevor die Klasse selbst finalized wird).
Wie hier schon 1000000mal geposted, Leute, lest doch einfach mal die Dokumentation, daß kann enorm helfen. Zum Thema Destruktoren schreibt die C# Doku z.B.
http://msdn2.microsoft.com/en-us/library/66x5fx1b.aspx
Darin wird auch beschrieben wie man z.B. in einer Situation wie Deiner das IDisposeable-Interface benutzen kann um explizit eine Resource zu schließen.
endline schrieb:
(Ich gehe davon aus, dass beim schliessen ein C++ aehnliches ostream::flush stattfindet.)
Auch hier.. warum raten wenn man es doch nachlesen kann? Hier http://msdn2.microsoft.com/en-us/library/system.io.stream.aspx steht ganz klar drin das Close ein flush auf streams aufruft.
Zurück zu deinem Problem, auch hier findet man den entsprechenden Hinweis in der Doku: http://msdn2.microsoft.com/en-us/library/ms143450.aspx
You should release all resources by specifying true for disposing. When disposing is true, the stream can also ensure data is flushed to the underlying buffer, and access other finalizable objects. This may not be possible when called from a finalizer due a lack of ordering among finalizers.
Da Dein Stream von der GC finalized wird ist es sehr wahrscheinlich das der letzte Satz zutrifft.
Schlußfolgerung:
a) Du solltest lernen wie die GC von C# funktioniert. Das gehört zum Basiswissen der Sprache.
b) deine "MeineKlasse" muß das IDisposeable-Interface implementieren um so den Stream explizit zu Closen bevor die Klasse finalized wird. Nur dann kann flush sicher funktionieren.
c) Alternativ, wenn auch nich ganz so schön könnte deine Schreib-Methode natürlich Flush benutzen...
d) Tu Dir selbst einen Gefallen und vergiß alles was Du über C++ weisst wenn Du C# programmierst. Die Sprachen mögen ähnlich aussehen, sind aber grundverschieden in vielen Aspekten.
-
Moin,
vergiss das mal mit IDisposable, das bringt dich nicht weiter, da der Stream eine verwaltete Resource ist und du daher immer noch den Zeitpunkt abpassen musst, zu dem der stream noch existiert (Dispose muss explizit aufgerufen werden, wenn der Stream noch existiert). Das kannst du auch einfacher haben.
a) Definiere dir in deiner Klasse eine Methode:
public CleanUp() { if( stream != null ) { stream.Close(); stream = null; } }
b) in deinem Hauptformular (bzw. in der hierarchisch ersten verwalteten Klasse, die eine Instanz deiner "MeineKlasse" enthält) fügst du folgende Aufräumarbeit ein...
protected override void Dispose( bool disposing ) { if( disposing ) { //---------- Verwaltete Resourcen aufräumen << Einfügen MeineKlassenInstanz.CleanUp(); //<< Einfügen if (components != null) { components.Dispose(); } } base.Dispose( disposing ); }
IMO ist das übrigens ein Designfehler der Streamklasse, die sollte sich meinem Verständnis nach so verhalten, dass der Stream beim Zerstören der Instanz automatisch geflusht und geschlossen wird - naja, mein Verständnis kann aber auch mangelhaft sein. *g*
-
OK, danke.
-
SolidOffline schrieb:
vergiss das mal mit IDisposable, das bringt dich nicht weiter, da der Stream eine verwaltete Resource ist und du daher immer noch den Zeitpunkt abpassen musst, zu dem der stream noch existiert (Dispose muss explizit aufgerufen werden, wenn der Stream noch existiert).
Du vermischst da Gutes mit Schlechtem:
– „Dispose muss explizit aufgerufen werden“: gut und richtig,
– „vergiss das mit dem IDisposable“: schlecht.Das kannst du auch einfacher haben.
Eigentlich nicht. Ich bin zwar auch kein Fan des Disposable Patterns aber unter .NET ist er die beste Möglichkeit, an RAII heranzukommen.
=> Fazit: Wenn eine Klasse mit Ressourcen arbeitet, die unverwaltet sind oder mit verwalteten Ressourcen, die selbst IDisposable sind, dann sollte diese Klasse das Disposable Pattern implementieren und auch benutzen. 'using' ist Dein Freund.
using (MeineKlasse x = new MeineKlasse()) { // Benutze x } // x.Dispose() wird automatisch aufgerufen, *egal, was passiert*!
IMO ist das übrigens ein Designfehler der Streamklasse, die sollte sich meinem Verständnis nach so verhalten, dass der Stream beim Zerstören der Instanz automatisch geflusht und geschlossen wird - naja, mein Verständnis kann aber auch mangelhaft sein. *g*
Hier gebe ich Dir zwar gefühlsmäßig recht, aber das ist nicht so ganz einfach, denn man kann ja Streams übereinanderlegen (z.B. einen CryptoStream über einen FileStream) und dann ist es eben *nicht* unbedingt wünschenswert, wenn der unterliegende Stream geflusht (oder sogar geschlossen) wird, nur weil der darüberliegende Stream nicht mehr benötigt wird.
-
Konrad Rudolph schrieb:
[...]
[-1-]
Du vermischst da Gutes mit Schlechtem:– „Dispose muss explizit aufgerufen werden“: gut und richtig,
– „vergiss das mit dem IDisposable“: schlecht.[-2-]
Das kannst du auch einfacher haben.
Eigentlich nicht. Ich bin zwar auch kein Fan des Disposable Patterns aber unter .NET ist er die beste Möglichkeit, an RAII heranzukommen.
=> Fazit: Wenn eine Klasse mit Ressourcen arbeitet, die unverwaltet sind oder mit verwalteten Ressourcen, die selbst IDisposable sind, dann sollte diese Klasse das Disposable Pattern implementieren und auch benutzen. 'using' ist Dein Freund.
using (MeineKlasse x = new MeineKlasse()) { // Benutze x } // x.Dispose() wird automatisch aufgerufen, *egal, was passiert*!
[-3-]
IMO ist das übrigens ein Designfehler der Streamklasse, die sollte sich meinem Verständnis nach so verhalten, dass der Stream beim Zerstören der Instanz automatisch geflusht und geschlossen wird - naja, mein Verständnis kann aber auch mangelhaft sein. *g*
Hier gebe ich Dir zwar gefühlsmäßig recht, aber das ist nicht so ganz einfach, denn man kann ja Streams übereinanderlegen (z.B. einen CryptoStream über einen FileStream) und dann ist es eben *nicht* unbedingt wünschenswert, wenn der unterliegende Stream geflusht (oder sogar geschlossen) wird, nur weil der darüberliegende Stream nicht mehr benötigt wird.
Zu [-1-]:
Das "vergessen sollen" war jetzt nicht generell auf IDisposable bezogen, sondern auf diesen konkreten Fall. Warum die IDisposable Schnittstelle in diesem Fall meiner Meinung nach kein bißchen weiterhilft siehe weiter unten.
Zu [-2-]:
An "using" habe ich zugegebenermaßen gar nicht gedacht, aber inwiefern hilft mir das weiter, wenn ich eben *nicht* genau weiß, wann mein Objekt zerstört wird, das eine Stream-Instanz beinhaltet? Nach meinem Verständnis muss ich, um die using-Anweisung nutzen zu können, a) genau den Bereich festlegen in dem die Instanz meines Objekts "lebt" ( { und } ) und b) die Instanz des Objekts im "using-Header" erstellen.
Das Stream-Objekt wird aber nunmal im Konstruktor meines Objekts erstellt, mein Objekt im Konstruktor eines anderen Objekts erstellt, usw. Mit anderen Worten: die Lebensdauer meines Objekts und damit auch des Stream-Objekts, lässt sich nicht in einen bestimmten (Code-)Bereich einkapseln.
Dies hat zur Folge, dass ich Dispose leider explizit aufrufen müsste (genauso wie die CleanUp-Methode) und zwar zu einem Zeitpunkt, an dem das Stream-Objekt noch nicht geschlossen wurde. Meiner Meinung nach ist die CleanUp-Methode hier transparenter und "direkter" als die Verwendung einer IDisposable-Schnittstelle inkl. Behandlung von Sonderfällen ( disposing true oder false? bool disposed im Objekt definieren etc. ).
Zu [-3-]:
IMO ist hier die Frage, was passieren sollte, wenn ein Stream-Objekt zur Zerstörung ansteht, dessen Daten aber noch nicht geflusht sind. Eine Designfrage. Ein Flag wäre hier doch ganz nett ( bool flushwhendisposed ), oder?
PS: falls ich bezgl. der using-Anweisung etwas falsch verstanden haben sollte, bitte ich um Aufklärung.
-
Solidoffline schrieb:
An "using" habe ich zugegebenermaßen gar nicht gedacht, aber inwiefern hilft mir das weiter, wenn ich eben *nicht* genau weiß, wann mein Objekt zerstört wird, das eine Stream-Instanz beinhaltet? Nach meinem Verständnis muss ich, um die using-Anweisung nutzen zu können, a) genau den Bereich festlegen in dem die Instanz meines Objekts "lebt" ( { und } ) und b) die Instanz des Objekts im "using-Header" erstellen.
Das Stream-Objekt wird aber nunmal im Konstruktor meines Objekts erstellt, mein Objekt im Konstruktor eines anderen Objekts erstellt, usw. Mit anderen Worten: die Lebensdauer meines Objekts und damit auch des Stream-Objekts, lässt sich nicht in einen bestimmten (Code-)Bereich einkapseln.
Es gibt genau drei Bereiche, in denen Disposable-Objekte zum Einsatz kommen:
- In lokalen Blöcken,
- als Mitglieder in Klassen und
- als Mitglieder von Klassen, die aber von außen übergeben werden.
Diese Fälle benötigen unterschiedliche Behandlung:
Der erste Fall ist trivial: wir benutzen die Variable in einem 'using'-Block.
Der zweite Fall ist der, von dem Du sprachst. Zum Glück ist er aber auch leicht schematisch lösbar: Man macht einfach die gesamte Klasse disposable (indem sie eben die Schnittstelle implementiert) und entsorgt das fragliche Objekt in der 'Dispose'-Methode der Klasse; gewissermaßen so:
class MyClass : IDisposable { private IDisposable m_MyVar; private bool m_Disposed; public MyClass() { m_MyVar = new SomeType; } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (!m_Disposed) { if (disposing) { m_MyVar.Dispose(); } m_Disposed = true; } } public ~MyClass() { Dispose(false); } }
Diese Klasse implementiert das komplette Disposable Pattern (hoffe ich; ich habe das jetzt aus dem Kopf reproduziert). Wichtig ist hierbei, dass es das explizite Entsorgen mittels 'Dispose()' und das implizite mittels 'Finalize' gibt. Im letzten Fall werden unsere managed resources *nicht* freigegeben, weil sie dies eventuell schon sind (denn der Finalizer in .NET ist ja nicht-deterministisch).
Genaueres hierzu kann man in der MSDN nachlesen. Wichtig ist bloß, dass alle Fälle aus 1. und 2. letztendlich soweit reduziert werden können, dass sie irgendwie in einem 'using'-Block stehen oder direkt vom Anwendungs-Framework entsorgt werden (also wird eine Instanz der Beispielklasse 'MyClass' entweder selbst in einem 'using'-Block verwendet oder wiederum in einer Klasse, die das Disposable-Pattern implementiert). Es sind also alle wichtigen Fälle abgedeckt.
Der dritte Fall macht leider aber Problem, die IMHO in .NET nicht gut lösbar sind. Hier meine ich zum Beispiel Fälle wie die folgende kleine Klasse:
class Circle : IDisposable { private Pen m_Pen; private Rectangle m_Rect; public Circle(Pen pen, Rectangle rect) { m_Pen = pen; m_Rect = rect; } public void DrawMe(Graphics g) { g.DrawEllipse(m_Pen, m_Rect); } public void Dispose() { m_Pen.Dispose(); } }
Diese Klasse übernimmt die Besitzrechte der Pen-Ressource. Das ist praktisch, denn dadurch muss man sich beim Anlegen nicht mehr um den Pen kümmern, nur noch um den Circle:
using (Circle c = new Circle(new Pen(Color.Chartreuse), myRect)) { // … }
Leider fällt man bei sowas furchtbar auf die Nase, wenn man stattdessen folgendes macht:
using (Circle c = new Circle(Pens.Chartreuse)) { // … }
Der Code ist quasi identisch, nur dass er eine vom Betriebssystem vor-reservierte Ressource benutzt. Leider crasht dieser Code, sobald versucht wird, diesen Spezial-Stift zu entsorgen. Hier hilft leider nur, eine eventuelle Ausnahme abzufangen – oder auf die Übernahme des Ressourcen-Besitzes zu verzichten. Beides extrem hässliche „Lösungen“.
– Allerdings ist der Fall 3 zum Glück relativ selten relevant. Aufpassen muss man aber zum Beispiel auch dann, wenn man mit nicht-synchronen Abläufen arbeitet, zum Beispiel mit Threads.
Zu [-3-]:
IMO ist hier die Frage, was passieren sollte, wenn ein Stream-Objekt zur Zerstörung ansteht, dessen Daten aber noch nicht geflusht sind. Eine Designfrage. Ein Flag wäre hier doch ganz nett ( bool flushwhendisposed ), oder?
Ja, ein Flag wäre in der Tat nett, könnte aber auch für Chaos sorgen. Am wichtigsten ist hier eigentlich eine sehr klare Dokumentation, da jede Variante Vor- und Nachteile sowie Stolperfallen hat. Mit der MSDN sind wir in .NET eigentlich schon verwöhnt, was Dokumentation anbelangt. Trotzdem wünsche ich mir auf solchen Gebieten eigentlich noch wesentlich mehr Klarheit und vor allem mehr warnende Hinweise auf möglicherweise überraschendes Verhalten.
-
Konrad Rudolph schrieb:
[...]
Genaueres hierzu kann man in der MSDN nachlesen. Wichtig ist bloß, dass alle Fälle aus 1. und 2. letztendlich soweit reduziert werden können, dass sie irgendwie in einem 'using'-Block stehen oder direkt vom Anwendungs-Framework entsorgt werden (also wird eine Instanz der Beispielklasse 'MyClass' entweder selbst in einem 'using'-Block verwendet oder wiederum in einer Klasse, die das Disposable-Pattern implementiert). Es sind also alle wichtigen Fälle abgedeckt.
[...]So weit so gut. Aaaaaaber...
Using-Block scheidet, aus den vorher schon genannten Gründen aus (Objekt hat keine durch einen bestimmten Code-Bereich begrenzte Lebensdauer).
Was deiner Ansicht nach jetzt übrib bliebe wäre also sowohl für "MyClass", also die das Stream-Objekt enthaltende Klasse, als auch für die Klasse, die "MyClass" enthält, die IDisposable-Schnittstelle zu implementieren und alles ist "in Butter". Nun, das habe ich nun extra mal ausprobiert und ich habe festgestellt, dass es selbst dann wieder darauf hinausläuft, Dispose() explizit aufrufen zu müssen... aber erstmal hier die Zutaten für den Test:
Wir haben:
- ein Hauptformular "MainWin" (Standard-Net-Windows-Anwendung) mit einer vordesignten Dispose()-Methode.
- eine Klasse "MyStreamClass", die ein Streamobjekt, instanziiert im Konstruktor, enthält.
- eine Klasse "MyClass", die ein Objekt der Klasse "MyStreamClass", instanziiert im Konstruktor, enthält.
- "MainWin" enthält ein Objekt der Klasse "MyClass", instanziiert im Konstruktor von "MainWin".
- "MyStreamClass" und "MyClass" haben die IDisposable-Schnittstelle mit allem Pipapo implementiert (alles gemäß MSDN) und deinem korrekten Beispiel.
[...Klasse MainWin (Standard-Net-Hauptformular)] public MainWin() { InitializeComponent(); p_MyClass = new MyClass(); } [...] protected override void Dispose( bool disposing ) { if( disposing ) { p_MyClass.Dispose(); // << Expliziter Aufruf, muss leider sein! >> if( components != null ) { components.Dispose(); } } base.Dispose( disposing ); } [...Klasse MyClass] private MyStreamClass p_MyStreamClass = new MyStreamClass(); private bool p_Disposed = false; ~MyClass() { Dispose( false ); } private void Dispose( bool disposing ) { if( !p_Disposed ) { if( disposing ) { // Dispose managed resources. p_MyStreamClass.Dispose(); // << Expliziter Aufruf, muss leider sein! >> } } p_Disposed = true; } public void Dispose() { Dispose( true ); GC.SuppressFinalize( this ); } [...Klasse MyStreamClass] private bool p_Disposed = false; ~MyStreamClass() { Dispose( false ); } private void Dispose( bool disposing ) { if( !p_Disposed ) { if( disposing ) { // Dispose managed resources. p_SWriter.Close(); } } p_Disposed = true; } public void Dispose() { Dispose( true ); GC.SuppressFinalize( this ); } [...]
Der Test lief nun drauf hinaus, dass die Dispose-Methoden jeweils trotzdem explizit aufgerufen werden mussten (automatischer Aufruf aus Finalizer funktioniert nicht, wie wir schon festgestellt haben), also keinerlei Gewinn gegenüber meiner vorgeschlagenen CleanUp-Methode. Im Gegenteil: mehr Code, jeweils eine zusätzliche Member-Variable (p_Disposed), "abstrakterer", "undurchsichtigerer" Code.
Ich bezweifle nicht, dass die IDisposable-Schnittstelle in anderen Fällen Vorteile bringt bzw. unerlässlich (nicht verwaltete Resourcen) ist, in diesem speziellen Fall jedoch bringt sie mir meiner Meinung nach überhaupt nichts bzw. nur Nachteile. Deshalb auch meine ursprüngliche Aussage "vergiss das mit IDisposable".
Nun will ich nicht ausschließen, dass ich hier etwas bedeutendes übersehen habe, also falls ja würde ich mich freuen, wenn ich darauf gestoßen werde.
-
SolidOffline schrieb:
also keinerlei Gewinn gegenüber meiner vorgeschlagenen CleanUp-Methode. Im Gegenteil: mehr Code, jeweils eine zusätzliche Member-Variable (p_Disposed), "abstrakterer", "undurchsichtigerer" Code.
Inwiefern „mehr Code“? Statt dem Aufruf von 'CleanUp' halt 'Dispose'. Mehr Code nur insofern, als dass Du das Disposable-Pattern implementieren musst. Gut, das ist schade (in VB übernimmt das die IDE für Dich) aber dafür kannst Du Dir ja ein Snippet schreiben.
Der ganz enorme Vorteil vor Deiner Methode ist: Es funktioniert auch automatisch. 'Dispose' wird nämlich eben *doch* automatisch aufgerufen, im Finalizer. Die Frage ist nur eben, *wann* das passiert. Wenn man das Program im Debug-Modus startet, kann in der Tat der Eindruck entstehen, dass das selbst beim Beenden des Programms noch nicht geschehen ist. Das liegt aber an der IDE. Im Release-Modus wird der Finalizer aller Objekte wirklich spätestens mit Beenden des Programms ausgeführt.
Und jetzt etwas WICHTIGES: Benutz immer IDisposable und nie Deine CleanUp-Methode! Ein Freund von mir muss gerade eine kommerzielle Fremdkomponente benutzen, die genau diesen Fehler begeht: sie implementiert kein IDisposable sondern eine eigene CleanUp-Methode. Das Resultat ist ein furchtbares Chaos, weil hier nämlich das CleanUp *wirklich* nie ausgeführt wird. Besagter Freund kann nicht mal einen Wrapper um die Klasse schreiben um das Problem zu beheben, denn dazu bräuchte er einen deterministischen Finalizer (im Finalizer der Wrapper-Klasse ist die Komponente schon entladen worden – ohne, dass die CleanUp-Methode ausgeführt worden ist). Um das Problem zu lösen, muss er im FormClosing-Event der Form den CleanUp-Code explizit aufrufen. Diese Methode ist aber *enorm* fehleranfällig, denn im Falle eines kritischen Beendens wird das FormClosing-Event nicht aufgerufen und es wird nicht ordentlich aufgeräumt. Das ist aber leider fatal, da die CleanUp-Methode dafür zuständig ist, Datenverbindungen auf einem Server abzumelden. Würde die Komponente die IDisposable-Schnittstelle implementieren, wäre das ganze Problem nicht vorhanden.
=> Der Autor dieser Komponente hat echt Prügel verdient, und da es sich um eine kommerzielle Komponente handelt, wird er die in irgendeiner Form auch bekommen.
-
Konrad Rudolph schrieb:
SolidOffline schrieb:
also keinerlei Gewinn gegenüber meiner vorgeschlagenen CleanUp-Methode. Im Gegenteil: mehr Code, jeweils eine zusätzliche Member-Variable (p_Disposed), "abstrakterer", "undurchsichtigerer" Code.
[-1-]
Inwiefern „mehr Code“? Statt dem Aufruf von 'CleanUp' halt 'Dispose'. Mehr Code nur insofern, als dass Du das Disposable-Pattern implementieren musst. Gut, das ist schade (in VB übernimmt das die IDE für Dich) aber dafür kannst Du Dir ja ein Snippet schreiben.[-2-]
Der ganz enorme Vorteil vor Deiner Methode ist: Es funktioniert auch automatisch. 'Dispose' wird nämlich eben *doch* automatisch aufgerufen, im Finalizer. Die Frage ist nur eben, *wann* das passiert. Wenn man das Program im Debug-Modus startet, kann in der Tat der Eindruck entstehen, dass das selbst beim Beenden des Programms noch nicht geschehen ist. Das liegt aber an der IDE. Im Release-Modus wird der Finalizer aller Objekte wirklich spätestens mit Beenden des Programms ausgeführt.[-3-]
Und jetzt etwas WICHTIGES: Benutz immer IDisposable und nie Deine CleanUp-Methode! Ein Freund von mir muss gerade eine kommerzielle Fremdkomponente benutzen, die genau diesen Fehler begeht: sie implementiert kein IDisposable sondern eine eigene CleanUp-Methode. Das Resultat ist ein furchtbares Chaos, weil hier nämlich das CleanUp *wirklich* nie ausgeführt wird. Besagter Freund kann nicht mal einen Wrapper um die Klasse schreiben um das Problem zu beheben, denn dazu bräuchte er einen deterministischen Finalizer (im Finalizer der Wrapper-Klasse ist die Komponente schon entladen worden – ohne, dass die CleanUp-Methode ausgeführt worden ist). Um das Problem zu lösen, muss er im FormClosing-Event der Form den CleanUp-Code explizit aufrufen. Diese Methode ist aber *enorm* fehleranfällig, denn im Falle eines kritischen Beendens wird das FormClosing-Event nicht aufgerufen und es wird nicht ordentlich aufgeräumt. Das ist aber leider fatal, da die CleanUp-Methode dafür zuständig ist, Datenverbindungen auf einem Server abzumelden. Würde die Komponente die IDisposable-Schnittstelle implementieren, wäre das ganze Problem nicht vorhanden.=> Der Autor dieser Komponente hat echt Prügel verdient, und da es sich um eine kommerzielle Komponente handelt, wird er die in irgendeiner Form auch bekommen.
Zu -1-:
Nun, du hast a) eine Member-Variable, die ich nicht brauche (bool p_Disposable) und b) zwei Methoden, die ich nicht brauche ( Finalizer + void Dispose( bool disposing ). Das ist "etwas" mehr Code gegenüber einem einfachen void CleanUp() { if( p_Stream != null ) { p_Stream.Close(); p_Stream = null; } }
Zu -2-:
Nein. *g* Der Finalizer wird zwar ausgeführt, aber er funktioniert eben nicht. Es bringt mir *nichts*, wenn das Close im Finalizer ausgeführt wird, im Gegenteil, es fliegt eine Exception, da das unterliegende Streamobjekt schon aufgeräumt und "geschlossen" wurde. Es ist *zwingend* notwendig, "MyStreamClass" explizit dann aufzuräumen (stream closen), wenn das Streamobjekt selbst noch nicht vom GC angefasst wurde. Das ist beim Dispose über den Finalizer aber leider nicht der Fall --> Dispose *muss* explizit aufgerufen werden.
Zu -3-:
Siehe oben. Der Finalizer hilft mir Null. Ich muss Dispose sowieso explizit aufrufen, ansonsten passiert da automatisch gar nix. Und ob ich nun Dispose explizit aufrufe, oder CleanUp, ist in dem Fall Jacke wie Hose. Einen Vorteil, der für Dispose spräche sehe ich aber doch: ich wäre gewappnet für den Fall, dass MyStreamClass evtl. irgendwann mal doch über den using-Block benutzt werden könnte, denn dort wird Dispose am Ende des Using-Blocks aufgerufen, wenn ich das richtig verstanden habe.
Schau dir mein Beispiel nochmal an: dort, wo in der Dispose-Methode des Hauptformulars jetzt explizit die Dispose-Methode von MyClass aufgerufen werden muss, muss ansonsten halt CleanUp explizit aufgerufen werden. Wird Dispose dort nicht explizit aufgerufen, passiert automatisch überhaupt nichts. Und ein Aufräumen im Finalizer kommt wie erwähnt nicht in Frage. Evtl. wäre das ja auch ein Tipp für deinen Freund (CleanUp in der Dispose-Methode des Hauptformulars aufrufen), obwohl ich nicht glaube, dass er daran noch nicht gedacht hat.
-
Solidoffline schrieb:
[...]
Nun, du hast a) eine Member-Variable, die ich nicht brauche (bool p_Disposable)
[...]Sorry, die Variable war "bool p_Disposed", also ein Flag, ob Objekt schon disposed wurde.
-
Solidoffline schrieb:
Nun, du hast a) eine Member-Variable, die ich nicht brauche (bool p_Disposable) und b) zwei Methoden, die ich nicht brauche ( Finalizer + void Dispose( bool disposing ). Das ist "etwas" mehr Code gegenüber einem einfachen void CleanUp() { if( p_Stream != null ) { p_Stream.Close(); p_Stream = null; } }
Na ja, da schafft ein Code-Snippet aber Abhilfe.
Nein. *g* Der Finalizer wird zwar ausgeführt, aber er funktioniert eben nicht. Es bringt mir *nichts*, wenn das Close im Finalizer ausgeführt wird, im Gegenteil, es fliegt eine Exception, da das unterliegende Streamobjekt schon aufgeräumt und "geschlossen" wurde.
Daher ja auch 'Dispose(false)' in einem solchen Fall. Macht ja aber nichts, denn zu diesem Zeitpunkt *ist* das unterliegende Stream-Objekt bereits ebenfalls ordnungsgemäß disposed worden.
Es ist *zwingend* notwendig, "MyStreamClass" explizit dann aufzuräumen (stream closen), wenn das Streamobjekt selbst noch nicht vom GC angefasst wurde.
Geht es jetzt um den Spezialfall von Flush() und Close()? Dann: OK. Wie gesagt: hier hat sich das .NET-Framework für eine Konvention entschieden (weil Dispose eben den unterliegenden Stream nicht schließt). Wenn die für Dich nicht praktikabel ist, dann muss natürlich explizit geschlossen werden. Falls es nicht um diesen Spezialfall geht: ist doch alles in Butter.
-
Konrad Rudolph schrieb:
Solidoffline schrieb:
Nun, du hast a) eine Member-Variable, die ich nicht brauche (bool p_Disposable) und b) zwei Methoden, die ich nicht brauche ( Finalizer + void Dispose( bool disposing ). Das ist "etwas" mehr Code gegenüber einem einfachen void CleanUp() { if( p_Stream != null ) { p_Stream.Close(); p_Stream = null; } }
[-1-]
Na ja, da schafft ein Code-Snippet aber Abhilfe.Nein. *g* Der Finalizer wird zwar ausgeführt, aber er funktioniert eben nicht. Es bringt mir *nichts*, wenn das Close im Finalizer ausgeführt wird, im Gegenteil, es fliegt eine Exception, da das unterliegende Streamobjekt schon aufgeräumt und "geschlossen" wurde.
[-2-]
Daher ja auch 'Dispose(false)' in einem solchen Fall. Macht ja aber nichts, denn zu diesem Zeitpunkt *ist* das unterliegende Stream-Objekt bereits ebenfalls ordnungsgemäß disposed worden.Es ist *zwingend* notwendig, "MyStreamClass" explizit dann aufzuräumen (stream closen), wenn das Streamobjekt selbst noch nicht vom GC angefasst wurde.
[-3-]
Geht es jetzt um den Spezialfall von Flush() und Close()? Dann: OK. Wie gesagt: hier hat sich das .NET-Framework für eine Konvention entschieden (weil Dispose eben den unterliegenden Stream nicht schließt). Wenn die für Dich nicht praktikabel ist, dann muss natürlich explizit geschlossen werden. Falls es nicht um diesen Spezialfall geht: ist doch alles in Butter.Zu -1-: Ja, aber mehr Code = mehr Code zu (über)lesen.
Zu -2-: Klar, ich kann auch einfach nix machen, wenn aus dem Finalizer heraus disposed wird (was ich im Beispiel ja auch mache), aber auch dann nüzt mir der Finalizer nix.
Zu -3-: Ja natürlich, es geht die ganze Zeit noch um den Streamfall vom Ursprungspost und die unangenehme Close-Problematik. Ansonsten würde ich die IDisposable-Schnittstelle immer dann verwenden, wenn nicht verwaltete Resourcen im Spiel sind, oder aber die Lebensdauer der Objekte der jeweiligen Klasse explizit beendet werden soll (z.B. aus Performancegründen - dein Pen-Beispiel war da sehr schön). Abzuwägen ist dann noch je nachdem, ob ein Aufräumen im Finalizer (Destruktor) nicht ausreicht, also auch in so einem Fall könnte man dann auf die IDisposable-Schnittstelle verzichten. Wobei ich jedoch glaube mich zu erinnern, dass der Weg über IDisposable performanter ist.