Forum: PC-Programmierung try/catch/finally WTF


von Eddy C. (chrisi)


Lesenswert?

Hi Programmierer,

kann mir jemand den Sinn von finally in C# erklären?

Wo liegt der Unterschied zwischen:
1
  try
2
  {
3
     // try something
4
  }
5
  catch (Exception)
6
  {
7
     // handle something
8
  }
9
  finally
10
  {
11
     // do what must be done
12
  }

und
1
  try
2
  {
3
     // try something
4
  }
5
  catch (Exception)
6
  {
7
     // handle something
8
  }
9
  // do what must be done here

Außer, dass es hübscher aussieht?

Ein öfters gesehenes Beispiel im Netz ist auch noch "irritierend" (siehe 
deutscher Kommentar im Quelltext:
1
void ReadFile(int index)
2
{
3
    string path = @"c:\users\public\test.txt";
4
    System.IO.StreamReader file = new System.IO.StreamReader(path);
5
    char[] buffer = new char[10];
6
    try
7
    {
8
        file.ReadBlock(buffer, index, buffer.Length);
9
    }
10
    catch (System.IO.IOException e)
11
    {
12
        Console.WriteLine("Error reading from {0}. Message = {1}", path, e.Message);
13
    }
14
    finally
15
    {
16
        // Wieso wird file auf null geprüft? file wird außerhalb von
17
        // try initialisiert und wird daher immer ungleich null sein!
18
        if (file != null)
19
        {
20
            file.Close();
21
        }
22
    }
23
}

Daher hat folgender Code den gleichen Effekt:
1
void ReadFile(int index)
2
{
3
    string path = @"c:\users\public\test.txt";
4
    System.IO.StreamReader file = new System.IO.StreamReader(path);
5
    char[] buffer = new char[10];
6
    try
7
    {
8
        file.ReadBlock(buffer, index, buffer.Length);
9
    }
10
    catch (System.IO.IOException e)
11
    {
12
        Console.WriteLine("Error reading from {0}. Message = {1}", path, e.Message);
13
    }
14
    file.Close();
15
}

Wer kann ein sinnvolles/nützliches Beispiel zu finally zeigen?

von Dennis S. (eltio)


Lesenswert?

Bin kein C#-Programmierer, aber die Antwort findest du hier von "Kevin 
Pang":
http://stackoverflow.com/questions/547791/why-use-finally-in-c
1
The answer is that a lot of times the code inside your catch statement
2
will either rethrow an exception or break out of the current function.
3
With the latter code, the "alwaysDoThis();" call won't execute if the
4
code inside the catch statement issues a return or throws a new
5
exception.

Gruß
Dennis

: Bearbeitet durch User
von Peter II (Gast)


Lesenswert?

Eddy C. schrieb:
> Wo liegt der Unterschied zwischen:

Wenn man nicht alle Exception abfängt oder eine neue Exception wirft.
1
  try
2
  {
3
     // try something
4
  }
5
  catch(IOException )
6
  {
7
     throw new MyException("sdafasdf");
8
  }
9
  finally
10
  {
11
     // do what must be done
12
  }

von Eddy C. (chrisi)


Lesenswert?

Dennis S. schrieb:
> Bin kein C#-Programmierer, aber die Antwort findest du hier von "Kevin
> Pang":

Das ist die perfekte Diskussion mit schlüssiger Erklärung!

Peter II schrieb:
> Wenn man nicht alle Exception abfängt oder eine neue Exception wirft.

Jetzt habe ich's :-)

Thanks 2gether!

von Dennis R. (dennis_r93)


Lesenswert?

Das geht sogar soweit, dass selbst nach einem return der finally block 
noch ausgeführt wird.
1
try{
2
return 0;
3
}
4
catch{
5
return 1;
6
//oder throw new Exception();
7
}
8
finally{
9
//mach was
10
}

von Noch einer (Gast)


Lesenswert?

Oder falls Console.WriteLine() eine Exception wirft.

Das if (file != null) ? Normalerweise macht man das Open im Try, damit 
das Catch auch die Open-Exceptions bearbeitet. Falls die Exception vor 
dem Open auftritt, schmeißt das Close eine unsinnige Exception.

von Rolf M. (rmagnus)


Lesenswert?

finally ist ein Hilfskonstrukt, den man braucht, weil Objekte in C# ihre 
Ressourcen nicht beim Verlassen der Funktion automatisch freigeben.
Das ist der Nachteil, den man sich durch einen Garbage Collector 
einhandelt.

von N. A. (hannilein)


Lesenswert?

Sorry, but what does "WTF" mean?
Is that German speak?

von Peter (Gast)


Lesenswert?

N. A. schrieb:
> Sorry, but what does "WTF" mean?
> Is that German speak?

No ists English:
http://de.wiktionary.org/wiki/WTF

von rofl (Gast)


Lesenswert?

ROFL
Antwort wurde zwar schon gegeben aber da gibt's noch eine C Analogie bei
1
switch(irgendwas)
2
{
3
 case A: machA;
4
 break;
5
.
6
.
7
.
8
default: machXYZ
9
}
/klugscheißende :-P

von Der Andere (Gast)


Lesenswert?

Rolf M. schrieb:
> den man braucht, weil Objekte in C# ihre
> Ressourcen nicht beim Verlassen der Funktion automatisch freigeben.

Was ist das für ein Unfug?
Es ist eher eine elegante Methode garantiert Ressourcen freizugeben, die 
eben NICHT automatisch freigegeben werden, auch im Fehlerfall.

Oder wird in deiner Programmiersprache eine Datenbankverbindung, ein 
offenes File oder HTTP oder TCP Verbindung automatisch freigegeben wenn 
du die Funktion/Methode verlässt?

Klar, genau dann wenn du den Rechner ausschaltest :-)

von Peter II (Gast)


Lesenswert?

Der Andere schrieb:
> Oder wird in deiner Programmiersprache eine Datenbankverbindung, ein
> offenes File oder HTTP oder TCP Verbindung automatisch freigegeben wenn
> du die Funktion/Methode verlässt?

klar doch. In C++ wird jedes Destuktur bei verlassen vom 
Gültigkeitsbereich aufgerufen.

Damit hat man ein definiertes verhalten, was es (leider) bei C# so nicht 
gibt.

von Mark B. (markbrandis)


Lesenswert?

Peter II schrieb:
> Der Andere schrieb:
>> Oder wird in deiner Programmiersprache eine Datenbankverbindung, ein
>> offenes File oder HTTP oder TCP Verbindung automatisch freigegeben wenn
>> du die Funktion/Methode verlässt?
>
> klar doch. In C++ wird jedes Destuktur bei verlassen vom
> Gültigkeitsbereich aufgerufen.

Wenn das Objekt auf dem Stack liegt, ja. Und wieviele Objekte liegen auf 
dem Heap?

von Der Andere (Gast)


Lesenswert?

Mark B. schrieb:
> Wenn das Objekt auf dem Stack liegt, ja. Und wieviele Objekte liegen auf
> dem Heap?

Eben, gerade teure Objekte wie DB Verbindungen, TCP verbindungen, etc. 
oft genug nicht.

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

Mark B. schrieb:

> Wenn das Objekt auf dem Stack liegt, ja. Und wieviele Objekte liegen auf
> dem Heap?

In modernem C++ so gut wie keine. Das spielt aber keine Rolle, solange 
dass Objekt zur Verwaltung der Datenbank-Verbindung auf dem Stack liegt. 
Und in C++ kann man sogar unterbinden, dass solche Objekte auf dem heap 
alloziiert werden.

von Mark B. (markbrandis)


Lesenswert?

Torsten R. schrieb:
> In modernem C++ so gut wie keine. Das spielt aber keine Rolle, solange
> dass Objekt zur Verwaltung der Datenbank-Verbindung auf dem Stack liegt.
> Und in C++ kann man sogar unterbinden, dass solche Objekte auf dem heap
> alloziiert werden.

Bei der Masse an real existierenden Speicherlecks scheint es dann wohl 
nicht sehr viel modernen C++ Code zu geben. ;-)

von Michael B. (laberkopp)


Lesenswert?

Der Andere schrieb:
> Oder wird in deiner Programmiersprache eine Datenbankverbindung, ein
> offenes File oder HTTP oder TCP Verbindung automatisch freigegeben wenn
> du die Funktion/Methode verlässt?

Na ja, das ist der Grund für Destruktoren und Klassen.

In klassischem C
1
int func(int param)
2
{
3
    char *buffer=malloc(1024);
4
    if(!buffer) return 0;
5
    if(!param) // spät entdeckter Fehler
6
    {
7
        free(buffer); // ja, hier kann/muss man den Fall extra behandeln
8
        return 0; // Rückkehr
9
    }
10
    use(param,buffer);
11
    free(buffer);
12
    return 1;
13
}
Wenn man eine Programmiersprache mit Exceptions hat, funktioniert kein:
1
int func(int param)
2
{
3
    char *buffer=malloc(1024);
4
    if(!buffer) return 0;
5
    if(!param) throw(whatever); // bzw. longjmp(outofhere); in C
6
    use(param,buffer);
7
    free(buffer); // oops, not freed after throw/logjmp
8
    return 1;
9
}
man braucht Klassen mit Destruktoren, damit der Code gut wird
1
class staticbuffer
2
{
3
    char *ptr;
4
    staticbuffer() { ptr=NULL; }
5
    staticbuffer(int size) { ptr=malloc(size); }
6
    ~staticbuffer() { if(ptr) free(buffer); }
7
    char *get() { if(ptr) return ptr; else throw(std::bad_alloc()); }
8
}
9
10
void func(int param)
11
{
12
    staticbuffer buffer(1024);
13
    if(!param) throw(whatever);
14
    use(param,buffer.get());
15
    // returncode nicht mehr nötig, Fehler=exception
16
}
Exceptions und Klassen hängen also konzeptionell direkt zusammen.

(Syntaxfehler ungetestet)

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

Mark B. schrieb:

> Bei der Masse an real existierenden Speicherlecks scheint es dann wohl
> nicht sehr viel modernen C++ Code zu geben. ;-)

Die Speicherlecks kommen eher aus diesem C-Mit-Klassen Style (wie das 
Beispiel von Michael Bertrandt).

In zwei Projekten, in denen ich den direkten Vergleich hatte, war der 
Server in C++ geschrieben, der Client in einem Fall in Java und im 
anderen Fall in C#. In beiden Fällen, gab es überhaupt kein Problem mit 
Resource-Leakes auf dem Server und die Software lief Wochen und Monate 
ohne Problem durch. Die Clients hatten häufig Probleme mit irgend wo 
vergrabenen Referenzen, die noch erreichbar waren, aber nicht mehr 
erreichbar sein sollten.

Michael Bertrandt schrieb:

> Exceptions und Klassen hängen also konzeptionell direkt zusammen.

Nö, ich benutze im Embedded Bereich Klassen ohne Exceptions. Und 
Exceptions habe ich schon unter VMS implementiert gesehen und die OS-API 
hatte garantiert keine Klassen.

von Rolf M. (rmagnus)


Lesenswert?

Der Andere schrieb:
> Rolf M. schrieb:
>> den man braucht, weil Objekte in C# ihre
>> Ressourcen nicht beim Verlassen der Funktion automatisch freigeben.
>
> Was ist das für ein Unfug?
> Es ist eher eine elegante Methode garantiert Ressourcen freizugeben, die
> eben NICHT automatisch freigegeben werden, auch im Fehlerfall.

Richtig. Und in C++ braucht man diese "elegante Methode" nicht. Man hat 
stattdessen Destruktoren, die das auf einem objektorientierten Weg 
umsetzen.

> Oder wird in deiner Programmiersprache eine Datenbankverbindung, ein
> offenes File oder HTTP oder TCP Verbindung automatisch freigegeben wenn
> du die Funktion/Methode verlässt?

Ja. Einfaches Pseudocode-Beispiel:
1
void readFile(string filename)
2
{
3
    File file;
4
    file.open(filename);
5
    Buffer buffer = file.readAll();
6
    Parser parser;
7
    data_ = parser.parse(buffer);
8
}

Zugegeben etwas konstruiert, aber man kann sich vorstellen, worauf das 
rauslaufen soll. Mehrere Aktionen werden durchgeführt, und dafür werden 
temporär Ressourcen benötigt, die nachher wieder freizugeben sind.
Wenn die Funktion ihr Ende erreicht oder irgendwo zwischendrin eine 
Exception ausgelöst wird, wird in C++ alles wieder freigegeben. Der 
File-Destruktor kümmert sich dabei darum, nicht nur den Speicher, den 
das File-Objekt selbst für das File-Handle braucht, freizugeben, sondern 
auch die Datei zu schließen. Man muss nicht wissen, daß das Objekt 
intern noch andere Ressourcen als Speicher braucht und sich auch nicht 
explizit darum kümmern, diese von Hand freizugeben. So wird das Prinzip 
der Kapselung besser umgesetzt als in Java oder C#.
Ich kenne das Problem z.B. von Lotus Notes, bei dem beim Laden oder 
Speichern von Mail-Anhängen auf einem USB-Stick dieser nicht sauber 
ausgeworfen werden kann, bis man Notes beendet, da offenbar irgendwo 
vergessen wurde, die Datei wieder explizit zu schließen und diese 
deshalb für immer  (bzw. bis zum Programm-Ende) offen bleibt.
Das ist für mich das Problem mit Garbage Collecoren. Sie kümmern sich 
nicht ganzheitlich um ein vernünfiges Ressourcenmanagement, sondern nur 
um den Speicher. Alle anderen Ressourcen werden nicht automatisch 
freigegeben, und man hat auch keine Möglichkeit, es vernünftig zu 
automatisieren. Und genau deshalb gibt es "finally".

Mark B. schrieb:
>> In C++ wird jedes Destuktur bei verlassen vom Gültigkeitsbereich
>> aufgerufen.
>
> Wenn das Objekt auf dem Stack liegt, ja. Und wieviele Objekte liegen auf
> dem Heap?

Wenn man's richtig macht, nur wenige bis gar keine, um die man sich 
explizit kümmern muss. Dafür gibt's in C++ Container und Smart Pointer.

von Karl H. (kbuchegg)


Lesenswert?

Mark B. schrieb:
> Torsten R. schrieb:
>> In modernem C++ so gut wie keine. Das spielt aber keine Rolle, solange
>> dass Objekt zur Verwaltung der Datenbank-Verbindung auf dem Stack liegt.
>> Und in C++ kann man sogar unterbinden, dass solche Objekte auf dem heap
>> alloziiert werden.
>
> Bei der Masse an real existierenden Speicherlecks scheint es dann wohl
> nicht sehr viel modernen C++ Code zu geben. ;-)

Eher gibt es mehr sogenannte C++ Programmierer, die mit dem Begriff 
'RAII' nichts anfangen können. RAII führt recht problemlos zu 
Programmen, die keine Speicherlecks aufweisen. Nur muss man es 
konsequent umsetzen. Und daran hapert es halt meistens (und ich nehme 
mich da durchaus auch nicht davon aus - an der Konsequenz).

: Bearbeitet durch User
von Klopfer (Gast)


Lesenswert?

In C# gibt es auch noch "using", damit ist zumindest das explizite 
"finally" bzw. der Aufruf von Dispose/Close (*) unnötig. Man sollte 
natürlich in Klassen, die entsprechende Ressourcen auf Klassenebene 
besitzen, IDisposable implementieren.

(*) Was meistens, aber nicht immer äquivalent ist.

Michael B. schrieb:
> Wenn man eine Programmiersprache mit Exceptions hat, funktioniert
> kein:
> int func(int param)
> {
>     char *buffer=malloc(1024);
>     if(!buffer) return 0;
>     if(!param) throw(whatever); // bzw. longjmp(outofhere); in C
>     use(param,buffer);
>     free(buffer); // oops, not freed after throw/logjmp
>     return 1;
> }

Na ja, so ungefähr:
1
void func(int param)
2
{
3
  auto buffer = std::make_unique<char[]>(1024);
4
  if(!param) throw(whatever);
5
  use(param, buffer.get());
6
}

Was nicht bedeutet, dass man es so machen muss oder sollte.

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.