try/catch/finally WTF

OP #4276867
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?
#4276874
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
Gast #4276902
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.
Gast #4282374
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 :-)
Gast #4282381
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.
#4282397
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?
#4282425
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. ;-)
#4282431
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)
(Firma: Torrox.de) #4282481
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.
#4282612
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.
#4282624
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).
Gast #4282650
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.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren