Forum: PC-Programmierung c#: gescheiterten Stream im catch-Block schließen


von Matthias S. (da_user)


Lesenswert?

Tag,

ich möchte ein Programm seine Konfigurations.xml einlesen lassen. Ist 
diese Datei nicht vorhanden, gehe ich von einer Neuinstallation aus, und 
es soll das Konfigurationsfenster geöffnet werden.

Ich fange dies mit einer try-catch Anweisung ab:
1
        /// <summary>
2
        /// Lädt im aktuellen Programmpfad die Datei "settings.xml" in das Konfiguration-Objekt
3
        /// </summary>
4
        private void loadSettings()
5
        {
6
            XmlSerializer serializer = new XmlSerializer(typeof (Settings));
7
            FileStream fileStream;
8
            try
9
            {
10
                fileStream = new FileStream(Path.Combine(Application.StartupPath, "settings.xml"), FileMode.Open);
11
                Konfiguration = (Settings)serializer.Deserialize(fileStream);
12
            }
13
            catch //Wenn Datei nicht gefunden wurde
14
            {
15
                //Fehlermeldung
16
                MessageBox.Show("Konfigurationsdatei wurde nicht gefunden. Der Konfigurationsdialog wird geöffnet",
17
                    "Konfiguration nicht gefunden", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
18
                fileStream.Close();
19
                //Konfigurationsdialog öffnen
20
                ShowSettingsDialog();
21
                //und nochmal versuchen die Datei einzulesen
22
                loadSettings();
23
            }
24
        }

Nun habe ich das Problem, dass mir der Kompiler zu dem Befehl 
"fileStream.Close();" sagt, ich verwende hier eine nicht zugewiesene 
lokale Variable.
Was ist den hier los? Wie kann ich das Umgehen?

von Peter II (Gast)


Lesenswert?

Matthias S. schrieb:
> Was ist den hier los? Wie kann ich das Umgehen?

wenn die Datei nicht geöffnet werden kann, dann musst du sie auch nicht 
schließen!

Wenn es noch etas komplexer wird kann man

if ( fileStream is not null )
   fileStream.Close();

schreiben


Oder was viel besser ist:

using

http://msdn.microsoft.com/de-de/library/yh598w02(v=vs.80).aspx

von jmp (Gast)


Lesenswert?

Moin,

ist irgendwie auch klar: der fileStream ist ja in diesem Fall auch nicht 
instantiiert, da du im Catch-Block gelandet bist, also ist das Schließen 
desselben auch nicht notwendig.

Problematisch werden könnte es, wenns beim Deserialize knallt, der 
filsStream aber angelegt wurde. Deswegen würde ich im try-Block auch nur 
den fileStream versuchen zu öffnen, dann ist der definitiv nicht 
vorhanden, falls du im catch landest.

von Matthias S. (da_user)


Lesenswert?

Ah,.. ok.

Klarer Denkfehler.

Ich hätte gedacht, ich müsste den FilStream natürlich schließen, bevor 
der Catch-Block über den Umweg mit dem SettingsDialog die XML-Datei 
schreibt.

Denn:
ich habe da anscheinend noch irgendwo anders einen Denkfehler drinnen. 
Wenn ich den über den Catch-Block erscheinenden Dialog ordnungsgemäß 
schließe, wird zwar die Datei angelegt (da war erster Denkfehler), wird 
aber nicht eingelesen, die MessageBox taucht erneut auf, und der Dialog 
auch. Endlosschleife quasi.
Erst wenn ich das Programm beende und neu starte, klappts.

von D. I. (Gast)


Lesenswert?

Da hast du ein klassisches Anti-Pattern verwendet. Man vergewaltigt das 
Exception-Handling nicht für normale Programmlogik.

Du testest ob das File existiert mit File.Exists

http://msdn.microsoft.com/en-us/library/system.io.file.exists.aspx

Und je nachdem ob das true oder false ergibt öffnest du das File oder 
deinen Dialog

von Rolf M. (rmagnus)


Lesenswert?

D. I. schrieb:
> Du testest ob das File existiert mit File.Exists
>
> http://msdn.microsoft.com/en-us/library/system.io....
>
> Und je nachdem ob das true oder false ergibt öffnest du das File oder
> deinen Dialog

Ich kenne eigentlich auch eher den Weg, direkt zu versuchen, das File zu 
öffnen. Wenn es nicht existiert, bekommt man das ja beim Öffnungsversuch 
gleich mit. Wenn man das vohrer nochmal explizit abfragt, ist die 
gesamte Aktion nicht mehr atomar, und es kann ein Fehlverhalten geben, 
wenn zufällig gerade zwischen den beiden Teilaktionen die Datei angelegt 
oder entfernt wird. Sowas wurde sogar schon als Einfallstor für 
Schadsoftware verwendet.

von --- (Gast)


Lesenswert?

Rolf Magnus schrieb:
> Wenn man das vohrer nochmal explizit abfragt, ist die
> gesamte Aktion nicht mehr atomar, und es kann ein Fehlverhalten geben,
> wenn zufällig gerade zwischen den beiden Teilaktionen die Datei angelegt
> oder entfernt wird.

Du sprichst in einem Anwenderprogramm von atomarer Ausführung von 
Befehlen?

Davon ist überhaupt nichts atomar auch wenns nur eine Codezeile ist. 
Genau das gleiche Fehlverhalten kann auch so provoziert werden, das ist 
nicht mehr oder weniger zufällig.

Ins catch gehört keine normale Programmlogik.

von Matthias S. (da_user)


Lesenswert?

D. I. schrieb:
> Da hast du ein klassisches Anti-Pattern verwendet. Man vergewaltigt das
> Exception-Handling nicht für normale Programmlogik.

Ah,.. ok, danke. Ich werde das entsprechend Umbasteln.

Ist halt so, wenn man sich das Programmieren selbst beibringt. Gibt's 
eigentlich Lektüre zum Thema "sauberes Programmdesign"?

Ich hätte da allerdings nochmal ein kleines Problem, das sicherlich auch 
mit Anti-Pattern oder einfach Unerfahrenheit zu tun, aber ich steh da 
mal wieder da, wie der Ochs vor'm Berg.

code:
1
private void BtnSettings_Click(object sender, EventArgs e)
2
{
3
    //Dialog erstellen
4
    SettingsDia ConfigDialog = new SettingsDia(Konfiguration);
5
    //Dialog anzeigen und auswerten
6
    if (ConfigDialog.ShowDialog() == DialogResult.OK)
7
        {
8
            //wenn ok
9
            Konfiguration = ConfigDialog.Config;
10
            TextBoxWriteLine("Dialog OK");
11
        }
12
    else
13
        {
14
            //ansonsten
15
            TextBoxWriteLine("Dialog abgebrochen");
16
            TextBoxWriteLine(Konfiguration.role);
17
        }
18
}

Also, Button drücken, Dialog erscheint, wird mit zwei Buttons darin (OK 
& Abbrechen) geschlossen. Mit TextBoxWriteLine habe ich mir eine kleine 
Methode gebastelt, um in einer TextBox eine Zeile auszugeben, quasi 
meine Log-/Debug-Textbox.

Je nachdem, ob der Dialog mit Abbrechen oder mit OK geschlossen wird, 
landet das Progamm im Richtigen If-then-else-Zweig, das erkenne ich an 
meiner TextBox. Trotzdem wird beim Abbrechen der Wert 
"Konfiguration.role" in das geändert, was nach beenden des Dialoges in 
ConfigDialog.Config.role drinsteht. Obwohl im entsprechenden Zweig das 
Objekt Konfiguration gar nicht neu zugewiesen wird.

Ich hoffe das war verständlich..... ich versuchs nochmal:

Dialog öffnen - Variable ConfigDialog.Config.role von z.B. "none" in 
"server" ändern - Klick auf Abbrechen - Ausgabe: "Dialog abgebrochen - 
server" (statt "Dialog abgebrochen - none")

Wäre schön, wenn mir da wer helfen könnte ;-)

VG
Matthias

von D. I. (Gast)


Lesenswert?

Matthias S. schrieb:
> Ist halt so, wenn man sich das Programmieren selbst beibringt. Gibt's
> eigentlich Lektüre zum Thema "sauberes Programmdesign"?

Erfahrung, Literatur und Lernen von Besseren.

Eine mögliche Lektüre für C# und dem ganzen Kram wäre z.B.:

http://www.amazon.de/Professionell-entwickeln-mit-Visual-2012/dp/3836219549/ref=sr_1_1?ie=UTF8&qid=1360863001&sr=8-1

Dieses setzt aber schon Programmierkenntnisse voraus

Dann zu den Grundlagen von Design Patterns:

http://www.amazon.de/Patterns-Elements-Reusable-Object-Oriented-Software/dp/0201633612/ref=sr_1_1?ie=UTF8&qid=1360863055&sr=8-1

uswusfuvm

von Sven (Gast)


Lesenswert?

Hallo Matthias,
du übergibst das Objekt "Konfiguration" an den "ConfigDialog" per 
Reference. Das bedeutet, dass alle Änderungen an deinem "Original" 
durchgeführt werden. Die Zeile "Konfiguration = ConfigDialog.Config;" 
ist demnach gar nicht nötig um deinen Änderungen zu übernehmen.

Stichworte sind call by reference bzw. call by value...

MfG Sven

von Matthias S. (da_user)


Lesenswert?

D. I. schrieb:
> Erfahrung, Literatur und Lernen von Besseren.

Also ganz viel in Foren fragen? ;-)

Sven schrieb:
> Stichworte sind call by reference bzw. call by value...

Ich übergebe also das Objekt selbst, und nicht dessen Inhalt? Alles 
klar, mit deinen Stichwörtern werde ich dann (hoffentlich) schon 
weiterkommen.

Danke euch allen!

von Sven (Gast)


Lesenswert?

Bei call by reference wird nur die Adresse im Speicher übergeben. Das 
Object bleibt also wo es ist...

von --- (Gast)


Lesenswert?

Matthias S. schrieb:
> D. I. schrieb:
>> Erfahrung, Literatur und Lernen von Besseren.
>
> Also ganz viel in Foren fragen? ;-)

Nein, das würde meiner Meinung nach erst weiter hinten auf der Liste 
stehen. Stackoverflow ist da eine positive Ausnahme, da kriegt man oft 
kompetente Antwort, aber man sollte sich an deren Netiquette halten.
Ansonsten, in Foren wie dieses gibt es ein paar Wenige die wirklich 
Ahnung von der Materie haben (wie z.B. Karl-Heinz für C++/Computational 
Geometry Fragen oder Yalu wenns mal mathematisch wird) aber dafür viel 
zu viele die ihre unverdauten Halbweisheiten als gottgegebenes Dogma 
weiterverbreiten müssen.

von Frank M. (aktenasche)


Lesenswert?

keine ahnung ob es schon genannt wurde, aber try catch ist eigentlich 
fürs exception handling gedacht.

bessere lösung

if(File.Exists(Path.Combine(Application.StartupPath, "settings.xml"))...
else...

von Matthias S. (da_user)


Lesenswert?

So.. ich habe mich jetzt mal etwas in das Thema "call by 
value/reference" eingelesen und irgendwie nicht weitergekommen.

Also folgendes meine ich Verstanden zu haben:

klassische Variablen (int, bool, string,..) sind immer Call by Value, 
heißt: es wird der Wert der Variable übergeben. Soll die Referenz dazu 
übergeben werden (also quasi die "Verknüpfung"), brauche ich das 
Schlüsselwort "ref".

Objekte, bzw. Variablen die auf eine Klassendefinition basieren, werden 
Grundsätzlich als "Call by Reference" übergeben.

Wie ich das ganze jetzt aber dazu bekomme, dass das ganze als "call bye 
value" übergeben wird, habe ich allerdings noch nicht rausgefunden.

Ich poste jetzt einfach mal die ersten paar (hoffentlich relevanten) 
Zeilen meines Dialogquellcodes;
1
        /// <summary>
2
        /// Die in diesem Dialog geladene und eingestelle Kofiguration
3
        /// </summary>
4
        public Settings Config = new Settings();
5
        
6
        /// <summary>
7
        /// Einstellungs Dialog initalisieren und Aktuelle Konfiguration übergeben
8
        /// </summary>
9
        /// <param name="CurrentConfig">Aktuelle Konfiguration</param>
10
        public SettingsDia(Settings CurrentConfig)
11
        {
12
            InitializeComponent();
13
14
            //erstmal akutelle Konfiguration laden
15
            this.Config = CurrentConfig;
16
            //Dann Dialogfelder der aktuellen Konfiguration anpassen:
17
/*....*/
18
}

Ich bedanke mich auf jeden Fall schonmal für die bis jetzt geleistete 
Hilfe!

von D. I. (Gast)


Lesenswert?

Man kann in C# keine Objekttypen by value übergeben, die werden immer by 
reference übergeben

von Sven (Gast)


Lesenswert?

Hallo Matthias,
du hast es eigentlich schon richtig verstanden. Zu diesem Thema gibt es 
auch einen guten Artikel in der MSDN.

http://msdn.microsoft.com/de-de/library/s6938f28(VS.80).aspx

Wenn man ein Object kopieren will, meint man eigentlich klonen. Hier 
wäre das Interface „ICloneable“ dein Freund. Das käme dann „per Value“ 
am nächsten, ist aber bäh ;-(.

Nun zu deinem eigentlichen Problem. Du hast dein Objekt "Settings" an 
den "SettingsDialog" übergeben, hast sicherlich einige Auswahlfelder 
oder Textfelder, wo du eine Eingabe erwartest und hast zu guter Letzt 
auch einen OK- und einen Cancel-Button. Füge doch einfach im 
Ereignishandler deines OK-Buttons den Code ein, der die Werte der 
Eingabefelder in dein Object "Settings" übergibt. Wenn der Nutzer Cancel 
drückt, wird nichts unternommen...

MfG Sven

von Matthias S. (da_user)


Lesenswert?

Genau diese Idee ist mir auch gekommen, als ich meinen Quelltext nochmal 
angesehen habe.

Trotzdem vielen Dank für den Hinweis auf das klonen.

von Rolf M. (rmagnus)


Lesenswert?

--- schrieb:
> Rolf Magnus schrieb:
>> Wenn man das vohrer nochmal explizit abfragt, ist die
>> gesamte Aktion nicht mehr atomar, und es kann ein Fehlverhalten geben,
>> wenn zufällig gerade zwischen den beiden Teilaktionen die Datei angelegt
>> oder entfernt wird.
>
> Du sprichst in einem Anwenderprogramm von atomarer Ausführung von
> Befehlen?

Gut erkannt.

> Davon ist überhaupt nichts atomar auch wenns nur eine Codezeile ist.

Es ist ein System Call statt zwei. Das ist es, was zählt. Die Zahl der 
Codezeilen hat damit nichts zu tun.

> Genau das gleiche Fehlverhalten kann auch so provoziert werden, das ist
> nicht mehr oder weniger zufällig.

Dann hat aber der Betriebssystem-Kernel einen üblen Bug. Was denkst du, 
was passiert, wenn der beim Öffnen einer Datei Erfolg meldet, obwohl sie 
gar nicht geöffnet werden konnte, weil sie zufällig genau während des 
Öffnens gelöscht wurde?

> Ins catch gehört keine normale Programmlogik.

Ich sehe da das Problem auf der anderen Seite: Die Nichtexistenz der zu 
öffnenden Datei sollte eigentlich nicht zur Exception führen.

von A.Hellwig (Gast)


Lesenswert?

D. I. schrieb:
> Man kann in C# keine Objekttypen by value übergeben, die werden immer by
> reference übergeben

Falsch, siehe 
http://msdn.microsoft.com/en-us/library/vstudio/14akc2c7.aspx.

von A.Hellwig (Gast)


Lesenswert?

D. I. schrieb:
> Da hast du ein klassisches Anti-Pattern verwendet. Man vergewaltigt das
> Exception-Handling nicht für normale Programmlogik.
>
> Du testest ob das File existiert mit File.Exists
>

Nein, das stimmt nicht. Da ein Dateizugriff immer schiefgehen kann und 
man daher immer die Exceptions abfangen und entsprechend reagieren 
muss, ist ein Test mittels File.Exists vorher überflüssig.

von D. I. (Gast)


Lesenswert?

A.Hellwig schrieb:
> Nein, das stimmt nicht. Da ein Dateizugriff immer schiefgehen kann und
> man daher immer die Exceptions abfangen und entsprechend reagieren
> muss, ist ein Test mittels File.Exists vorher überflüssig.

Moment, nicht zwei Sachen mixen. Natürlich muss man Exceptions 
behandeln, aber man versteckt keine ordentliche Programmlogik im 
Exceptionhandling. Exceptionhandling ist das eine, Programmlogik das 
andere und daher ist es denke ich am Saubersten, dass beides gemacht 
wird. Ich prüfe erst mit File.Exists und wenn das fehlschlägt fahre ich 
im else-Teil mit Programmlogik fort und im Erfolgfall öffne ich die 
Datei und kümmere mich an dieser Stelle um potenzielle Exceptions.

von D. I. (Gast)


Lesenswert?

A.Hellwig schrieb:
> D. I. schrieb:
>> Man kann in C# keine Objekttypen by value übergeben, die werden immer by
>> reference übergeben
>
> Falsch, siehe
> http://msdn.microsoft.com/en-us/library/vstudio/14akc2c7.aspx.

Öhm wo widerspricht das meiner Aussage? Ich habe nicht gesagt, dass man 
primitive Typen nicht auch als Referenz übergeben kann.

Nun gut aber structs sind auch Wertetypen, so gesehen...

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.