Entwickler-Ecke
IO, XML und Registry - Dispose / CLose / Finalize - StreamWriter / Lebenszeit
FrankyBoy69 - So 26.04.09 16:41
Titel: Dispose / CLose / Finalize - StreamWriter / Lebenszeit
Hallo *,
ich bin neu bei C# und habe folgende Klasse erstellt:
C#-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43:
| using System; using System.Collections.Generic; using System.Linq; using System.Text;
using System.IO;
namespace MyWriter { class MyWriter { private StreamWriter _sr = null;
private static int _ctor_cnt = 0; private static int _dtor_cnt = 0;
public MyWriter(string fileName) { _ctor_cnt++; _sr = new StreamWriter(fileName); }
~MyWriter() { _dtor_cnt++;
Console.WriteLine("Ctor wurde {0} mal und Dtor {1} mal aufgerufen.", _ctor_cnt, _dtor_cnt);
if (_sr != null) { _sr.Close(); } }
public void Write(string theData) { _sr.WriteLine(theData); _sr.Flush(); } } } |
Verwendet wird die Klasse von der StandardForm so:
C#-Quelltext
1: 2: 3: 4: 5:
| private void butMyWriterTest_Click(object sender, EventArgs e) { MyWriter mw = new MyWriter("C:\\temp\\mw.txt"); mw.Write("Das soll eine Zeile werden."); } |
Nun zu meinen Problem:
Wenn ich die Form1 (und somit das Programm) beende, wird eine Exception ausgelöst bei folgendem Befehl:
_sr.Close();
Details zur Exception:
Zitat: |
System.ObjectDisposedException wurde nicht behandelt.
Message="Auf eine geschlossene Datei kann nicht zugegriffen werden."
Source="mscorlib"
ObjectName=""
StackTrace:
bei System.IO.FileStream.Flush()
bei System.IO.StreamWriter.Dispose(Boolean disposing)
bei System.IO.StreamWriter.Close()
bei MyWriter.MyWriter.Finalize()
InnerException: |
Falls ich das Ganze richtig verstehe, wird versucht an einen bereits geschlossenen Stream Close() aufzurufen. Was ich aber nicht verstehe ist: Wer hat denn den Stream geschlossen?
Ich ging davon aus, dass während der gesamten Lebenszeit der Klasse auch der Stream lebt. Und dann halt im Destruktor geschlossen werden muss.
Kann mir hier jemand auf die Sprünge helfen?
Danke im Voraus :shock:
Christian S. - So 26.04.09 16:51
Hallo und :welcome:!
Wenn ich das richtig sehe, wird während der Laufzeit Deines Programmes schon die Dispose-Methode Deines StreamWriters durch die Garbage Collection aufgerufen und dann beim Schließen des Programmes nochmals Close durch Deinen Finalizer.
Da Deine Klasse via StreamWriter unmanaged Resources belegt, solltest Du in Deiner Klasse den IDisposable-Pattern implementieren. Darin ist dann auch der Aufruf von GC.SuppressFinalize() enthalten, welcher obiges Problem lösen sollte.
Zu dem Pattern dürfte Google einiges liefern.
Grüße
Christian
Kha - So 26.04.09 17:33
FrankyBoy69 hat folgendes geschrieben : |
Und dann halt im Destruktor geschlossen werden muss. |
Nein, der Desktruktor ist allein zum Aufräumen von
unmanaged Ressourcen da. Wie du gesehen hast,
darfst du darin nicht einmal auf managed Ressourcen zugreifen, da die Reihenfolge der Finalize-Aufrufe unbestimmt ist.
Solange du also nur andere CLR-Klassen wie den StreamWriter benutzt, brauchst du überhaupt keinen Destruktor. Du
kannst dem Benutzer deiner Klasse aber helfen, knappe Ressourcen wie IO-Handles schneller (determiniert) freizugeben, indem du IDisposable implementierst und darin dann Dispose aller deiner managed Ressourcen aufrufst.
Edit: Es gibt noch das Pattern, dass eine Basisklasse IDisposable und einen Destruktor implementiert und beides an eine
protected override void Dispose(bool disposing) weiterleitet:
http://msdn.microsoft.com/en-us/library/fs2xkftw.aspx. IMHO aber unnötig, solange keine unmanaged Ressourcen freigegeben werden sollen (auch nicht in Subklassen), auch wenn ich langsam selbst nicht mehr durchblicke :lol: .
FrankyBoy69 - So 26.04.09 18:15
Hallo und danke für eure Antworten.
Ich bin erst einmal dem Ratschlag von Christian gefolgt und habe das Interface IDisposable implementiert.
C#-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19:
| #region IDisposable Member
public void Dispose() { if (_sr != null ) { _sr.Close(); }
Console.WriteLine("Object disposed.");
_sr = null;
GC.SuppressFinalize(this); }
#endregion |
Nun kommt keine Fehlermeldung mehr und das File wird wie erwartet geschrieben. Allerdings kommt auch keine Ausgabe der Zeile Console.WriteLine(...)
Also liegt die Vermutung nahe das die Methode gar nicht aufgerufen wird. Beim expliziten Methodenaufruf kommt auch die Ausgabe ...
Zitat: |
Solange du also nur andere CLR-Klassen wie den StreamWriter benutzt, brauchst du überhaupt keinen Destruktor. |
Ergo: Wenn ich einfach nix tue (Kein Implementierung eines Destruktors / Interfaces (IDisposable)) dann ist alles ok? Habe ich ausprobiert es funktioniert .. wie von Geisterhand ....
Eine komische Programiersprache ... :shock:
Kha - So 26.04.09 20:41
FrankyBoy69 hat folgendes geschrieben : |
Allerdings kommt auch keine Ausgabe der Zeile Console.WriteLine(...) |
Wie gesagt ist IDisposable nur ein Pattern, die CLR weiß nichts davon. Du musst Dispose selbst aufrufen oder einen using-Block benutzen.
Zitat: |
Ergo: Wenn ich einfach nix tue (Kein Implementierung eines Destruktors / Interfaces (IDisposable)) dann ist alles ok? Habe ich ausprobiert es funktioniert .. wie von Geisterhand .... |
Wobei es beim StreamWriter Probleme geben kann: Der Stream könnte vor dem Writer freigegeben werden, dann sind die noch im Puffer liegenden Daten futsch. Ein IDisposable, das den StreamWriter dispost, brauchst du also auf jeden Fall.
Zitat: |
Eine komische Programiersprache ... :shock: |
Da wohl 90% aller modernen Sprachen einen Garbage Collector verwenden, sind wir nicht in schlechter Gesellschaft ;) .
Entwickler-Ecke.de based on phpBB
Copyright 2002 - 2011 by Tino Teuber, Copyright 2011 - 2025 by Christian Stelzmann Alle Rechte vorbehalten.
Alle Beiträge stammen von dritten Personen und dürfen geltendes Recht nicht verletzen.
Entwickler-Ecke und die zugehörigen Webseiten distanzieren sich ausdrücklich von Fremdinhalten jeglicher Art!