it-swarm.com.de

Warum werden Variablen in "catch" oder "finally" nicht im Gültigkeitsbereich "try" deklariert?

In C # und in Java (und möglicherweise auch in anderen Sprachen) sind Variablen, die in einem "try" -Block deklariert wurden, in den entsprechenden "catch" - oder "finally" -Blöcken nicht im Gültigkeitsbereich. Der folgende Code wird nicht kompiliert:

try {
  String s = "test";
  // (more code...)
}
catch {
  Console.Out.WriteLine(s);  //Java fans: think "System.out.println" here instead
}

In diesem Code tritt beim Verweis auf s im catch-Block ein Fehler zur Kompilierungszeit auf, da s nur im try-Block enthalten ist. (In Java lautet der Kompilierungsfehler "s kann nicht behoben werden". In C # lautet er "Der Name 's' ist im aktuellen Kontext nicht vorhanden".)

Die allgemeine Lösung für dieses Problem scheint darin zu bestehen, Variablen statt innerhalb des try-Blocks direkt vor dem try-Block zu deklarieren:

String s;
try {
  s = "test";
  // (more code...)
}
catch {
  Console.Out.WriteLine(s);  //Java fans: think "System.out.println" here instead
}

Zumindest für mich jedoch (1) fühlt sich dies wie eine klobige Lösung an, und (2) führt dazu, dass die Variablen einen größeren Umfang als vom Programmierer beabsichtigt haben (der gesamte Rest der Methode, anstatt nur im Kontext der try-catch-finally).

Meine Frage ist, aus welchen Gründen (in Java, in C # und/oder in einer anderen anwendbaren Sprache) diese Entscheidung zur Sprachgestaltung getroffen wurde/wurden.

128
Jon Schneider

Zwei Dinge:

  1. Im Allgemeinen hat Java nur zwei Gültigkeitsbereiche: global und function. Try/catch ist jedoch eine Ausnahme (kein Wortspiel beabsichtigt). Wenn eine Ausnahme ausgelöst wird und dem Ausnahmeobjekt eine Variable zugewiesen wird dazu ist diese Objektvariable nur innerhalb des Abschnitts "catch" verfügbar und wird zerstört, sobald der catch abgeschlossen ist.

  2. (und wichtiger). Sie können nicht wissen, wo im try-Block die Ausnahme ausgelöst wurde. Möglicherweise wurde Ihre Variable vor ihrer Deklaration deklariert. Daher ist es unmöglich zu sagen, welche Variablen für die catch/finally-Klausel verfügbar sind. Betrachten Sie den folgenden Fall, in dem der von Ihnen vorgeschlagene Geltungsbereich gilt:

    
    try
    {
        throw new ArgumentException("some operation that throws an exception");
        string s = "blah";
    }
    catch (e as ArgumentException)
    {  
        Console.Out.WriteLine(s);
    }
    

Dies ist eindeutig ein Problem. Wenn Sie den Ausnahmehandler erreichen, wurde s nicht deklariert. Angesichts der Tatsache, dass Catches außergewöhnliche Umstände behandeln sollen und schließlich muss ausgeführt werden, ist es weitaus besser, sicher zu sein und dies zur Kompilierungszeit als Problem zu deklarieren als zur Laufzeit.

161

Wie können Sie sicher sein, dass Sie den Deklarationsteil in Ihrem catch-Block erreicht haben? Was ist, wenn die Instanziierung die Ausnahme auslöst?

55
Burkhard

Traditionell bleibt in C-Sprachen das, was in geschweiften Klammern geschieht, in den geschweiften Klammern. Ich denke, dass es für die meisten Programmierer uninteressant wäre, die Lebensdauer einer Variablen über solche Bereiche hinweg zu haben. Sie können erreichen, was Sie wollen, indem Sie die try/catch/finally-Blöcke in eine andere Ebene von Klammern einschließen. z.B.

... code ...
{
    string s = "test";
    try
    {
        // more code
    }
    catch(...)
    {
        Console.Out.WriteLine(s);
    }
}

EDIT: Ich denke, jede Regel hat eine Ausnahme. Folgendes gilt für C++:

int f() { return 0; }

void main() 
{
    int y = 0;

    if (int x = f())
    {
        cout << x;
    }
    else
    {
        cout << x;
    }
}

Der Gültigkeitsbereich von x ist die Bedingung, die then-Klausel und die else-Klausel.

17
Ferruccio

Alle anderen haben die Grundlagen angesprochen - was in einem Block passiert, bleibt in einem Block. Im Falle von .NET kann es jedoch hilfreich sein, zu untersuchen, was der Compiler für wahrscheinlich hält. Nehmen Sie zum Beispiel den folgenden Try/Catch-Code (beachten Sie, dass der StreamReader korrekt außerhalb der Blöcke deklariert ist):

static void TryCatchFinally()
{
    StreamReader sr = null;
    try
    {
        sr = new StreamReader(path);
        Console.WriteLine(sr.ReadToEnd());
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.ToString());
    }
    finally
    {
        if (sr != null)
        {
            sr.Close();
        }
    }
}

Dies wird in MSIL in etwa wie folgt kompiliert:

.method private hidebysig static void  TryCatchFinallyDispose() cil managed
{
  // Code size       53 (0x35)    
  .maxstack  2    
  .locals init ([0] class [mscorlib]System.IO.StreamReader sr,    
           [1] class [mscorlib]System.Exception ex)    
  IL_0000:  ldnull    
  IL_0001:  stloc.0    
  .try    
  {    
    .try    
    {    
      IL_0002:  ldsfld     string UsingTest.Class1::path    
      IL_0007:  newobj     instance void [mscorlib]System.IO.StreamReader::.ctor(string)    
      IL_000c:  stloc.0    
      IL_000d:  ldloc.0    
      IL_000e:  callvirt   instance string [mscorlib]System.IO.TextReader::ReadToEnd()
      IL_0013:  call       void [mscorlib]System.Console::WriteLine(string)    
      IL_0018:  leave.s    IL_0028
    }  // end .try
    catch [mscorlib]System.Exception 
    {
      IL_001a:  stloc.1
      IL_001b:  ldloc.1    
      IL_001c:  callvirt   instance string [mscorlib]System.Exception::ToString()    
      IL_0021:  call       void [mscorlib]System.Console::WriteLine(string)    
      IL_0026:  leave.s    IL_0028    
    }  // end handler    
    IL_0028:  leave.s    IL_0034    
  }  // end .try    
  finally    
  {    
    IL_002a:  ldloc.0    
    IL_002b:  brfalse.s  IL_0033    
    IL_002d:  ldloc.0    
    IL_002e:  callvirt   instance void [mscorlib]System.IDisposable::Dispose()    
    IL_0033:  endfinally    
  }  // end handler    
  IL_0034:  ret    
} // end of method Class1::TryCatchFinallyDispose

Was sehen wir? MSIL respektiert die Blöcke - sie sind Teil des zugrunde liegenden Codes, der beim Kompilieren von C # generiert wird. Der Geltungsbereich ist nicht nur in der C # -Spezifikation festgelegt, sondern auch in der CLR- und CLS-Spezifikation.

Der Bereich schützt Sie, aber Sie müssen ihn gelegentlich umgehen. Mit der Zeit gewöhnt man sich daran und es fühlt sich ganz natürlich an. Wie alle anderen sagten, bleibt das, was in einem Block passiert, in diesem Block. Sie möchten etwas teilen? Sie müssen außerhalb der Blöcke gehen ...

9
John Rudy

In C++ ist der Gültigkeitsbereich einer automatischen Variablen auf jeden Fall durch die geschweiften Klammern begrenzt, die sie umgeben. Warum sollte jemand damit rechnen, dass dies anders ist, wenn er ein Schlüsselwort try außerhalb der geschweiften Klammern einfügt?

8
ravenspoint

Wie ravenspoint betonte, erwartet jeder, dass Variablen lokal zu dem Block sind, in dem sie definiert sind. try führt einen Block ein und catch ebenfalls.

Wenn Sie lokale Variablen für try und catch möchten, versuchen Sie, beide in einen Block einzuschließen:

// here is some code
{
    string s;
    try
    {

        throw new Exception(":(")
    }
    catch (Exception e)
    {
        Debug.WriteLine(s);
    }
}
5
Daren Thomas

Die einfache Antwort ist, dass C und die meisten Sprachen, die seine Syntax geerbt haben, blockspezifisch sind. Das heißt, wenn eine Variable in einem Block definiert ist, d. H. Innerhalb von {}, ist dies ihr Gültigkeitsbereich.

Die Ausnahme ist übrigens JavaScript, das eine ähnliche Syntax hat, jedoch funktionsspezifisch ist. In JavaScript befindet sich eine in einem try-Block deklarierte Variable im catch-Block und an allen anderen Stellen in der enthaltenen Funktion im Gültigkeitsbereich.

5
dgvid

@burkhard hat die Frage, warum richtig geantwortet wurde, aber als Hinweis wollte ich hinzufügen, dass Ihr empfohlenes Lösungsbeispiel zwar gut 99,9999 +% der Zeit ist, es jedoch keine gute Praxis ist Instanziiere etwas innerhalb des try-Blocks oder initialisiere die Variable mit etwas, anstatt sie nur vor dem try-Block zu deklarieren. Beispielsweise:

string s = String.Empty;
try
{
    //do work
}
catch
{
   //safely access s
   Console.WriteLine(s);
}

Oder:

string s;
try
{
    //do work
}
catch
{
   if (!String.IsNullOrEmpty(s))
   {
       //safely access s
       Console.WriteLine(s);
   }
}

Dies sollte eine Skalierbarkeit der Problemumgehung ermöglichen, sodass Sie auch dann sicher auf die Daten Ihres catch-Blocks zugreifen können, wenn Ihre Arbeit im try-Block komplexer ist als die Zuweisung einer Zeichenfolge.

4
Timothy Carter

Gemäß dem Abschnitt mit dem Titel "Gewusst wie: Ausnahmen auslösen und abfangen" in Lektion 2 von MCTS Self-Paced Training Kit (Prüfung 70-536): Microsoft® .NET Framework 2.0 - Application Development Foundation der Grund ist, dass die Ausnahme möglicherweise vor Variablendeklarationen im try-Block aufgetreten ist (wie andere bereits bemerkt haben).

Zitat aus Seite 25:

Msgstr "" "Beachten Sie, dass die StreamReader - Deklaration im vorhergehenden Beispiel aus dem Try - Block heraus verschoben wurde. Dies ist notwendig, weil der finally - Block nicht auf Variablen zugreifen kann, die im Try - Block deklariert sind. Dies ist sinnvoll, weil Je nachdem, wo eine Ausnahme aufgetreten ist, wurden Variablendeklarationen im Try-Block möglicherweise noch nicht ausgeführt . "

4
hurst

Die Antwort lautet, wie jeder betont hat, "so werden Blöcke definiert".

Es gibt einige Vorschläge, um den Code schöner zu machen. Siehe ARM

 try (FileReader in = makeReader(), FileWriter out = makeWriter()) {
       // code using in and out
 } catch(IOException e) {
       // ...
 }

Closures sollen dies auch ansprechen.

with(FileReader in : makeReader()) with(FileWriter out : makeWriter()) {
    // code using in and out
}

UPDATE: ARM ist implementiert in Java 7. http : //download.Java.net/jdk7/docs/technotes/guides/language/try-with-resources.html

4
ykaganovich

Ihre Lösung ist genau das, was Sie tun sollten. Sie können nicht sicher sein, ob Ihre Deklaration im try-Block erreicht wurde, was zu einer weiteren Ausnahme im catch-Block führen würde.

Es muss einfach als separater Bereich funktionieren.

try
    dim i as integer = 10 / 0 ''// Throw an exception
    dim s as string = "hi"
catch (e)
    console.writeln(s) ''// Would throw another exception, if this was allowed to compile
end try
2
EndangeredMassa

Die Variablen sind auf Blockebene und auf diesen Try- oder Catch-Block beschränkt. Ähnlich wie beim Definieren einer Variablen in einer if-Anweisung. Denken Sie an diese Situation.

try {    
    fileOpen("no real file Name");    
    String s = "GO TROJANS"; 
} catch (Exception) {   
    print(s); 
}

Der String würde niemals deklariert, daher kann er nicht abhängig sein.

2
jW.

Weil der try-Block und der catch-Block zwei verschiedene Blöcke sind.

Würden Sie im folgenden Code erwarten, dass die in Block A definierten s in Block B sichtbar sind?

{ // block A
  string s = "dude";
}

{ // block B
  Console.Out.WriteLine(s); // or printf or whatever
}
2
François

Wie bereits von anderen Benutzern erwähnt, definieren die geschweiften Klammern den Gültigkeitsbereich in nahezu jeder mir bekannten Sprache im C-Stil.

Wenn es sich um eine einfache Variable handelt, warum interessiert es Sie dann, wie lange sie im Gültigkeitsbereich sein wird? Es ist keine so große Sache.

wenn es sich in C # um eine komplexe Variable handelt, möchten Sie IDisposable implementieren. Sie können dann entweder try/catch/finally verwenden und im finally-Block obj.Dispose () aufrufen. Sie können auch das Schlüsselwort using verwenden, das am Ende des Codeabschnitts automatisch Dispose aufruft.

1
Charles Graham

Was ist, wenn die Ausnahme in einem Code ausgelöst wird, der über der Deklaration der Variablen liegt? Das heißt, die Erklärung selbst war in diesem Fall nicht zufriedenstellend.

try {

       //doSomeWork // Exception is thrown in this line. 
       String s;
       //doRestOfTheWork

} catch (Exception) {
        //Use s;//Problem here
} finally {
        //Use s;//Problem here
}
1
Ravi

In dem von Ihnen angegebenen Beispiel kann beim Initialisieren von s keine Ausnahme ausgelöst werden. Sie würden also denken, dass der Anwendungsbereich möglicherweise erweitert werden könnte.

Im Allgemeinen können Initialisierungsausdrücke Ausnahmen auslösen. Es wäre nicht sinnvoll, wenn eine Variable, deren Initialisierung eine Ausnahme ausgelöst hat (oder die nach einer anderen Variablen deklariert wurde, bei der dies passiert ist), in den Bereich für catch/finally fällt.

Auch die Lesbarkeit des Codes würde darunter leiden. Die Regel in C (und den darauf folgenden Sprachen, einschließlich C++, Java und C #)) ist einfach: Variablenbereiche folgen Blöcken.

Wenn Sie möchten, dass eine Variable den Gültigkeitsbereich try/catch/finally hat, aber nirgendwo anders, dann wickeln Sie das Ganze in eine andere geschweifte Klammer (einen leeren Block) und deklarieren Sie die Variable vor dem Versuch.

1
Steve Jessop

Ein Grund dafür, dass sie sich nicht im selben Bereich befinden, ist, dass Sie an jedem Punkt des try-Blocks die Ausnahme auslösen können. Wenn sie sich im selben Bereich befänden, wäre es eine Katastrophe, denn je nachdem, wo die Ausnahme ausgelöst wurde, könnte sie noch mehrdeutiger sein.

Zumindest wenn es außerhalb des try-Blocks deklariert wird, wissen Sie genau, was die Variable mindestens sein könnte, wenn eine Ausnahme ausgelöst wird. Der Wert der Variablen vor dem try-Block.

1
zxcv

In Python sind sie in den catch/finally-Blöcken sichtbar, wenn die Zeile, die sie deklariert, nicht geworfen hat.

1
adal

Während es in Ihrem Beispiel seltsam ist, dass es nicht funktioniert, nehmen Sie dieses ähnliche:

    try
    {
         //Code 1
         String s = "1|2";
         //Code 2
    }
    catch
    {
         Console.WriteLine(s.Split('|')[1]);
    }

Dies würde bewirken, dass der Fang eine Nullreferenzausnahme auslöst, wenn Code 1 fehlerhaft ist. Nun, während die Semantik von try/catch ziemlich gut verstanden ist, wäre dies ein ärgerlicher Eckfall, da s mit einem Anfangswert definiert ist, so dass es theoretisch niemals null sein sollte, aber unter geteilter Semantik wäre es dies.

Auch dies könnte theoretisch behoben werden, indem nur getrennte Definitionen zugelassen werden (String s; s = "1|2";) oder eine andere Gruppe von Bedingungen, aber es ist im Allgemeinen einfacher, einfach nein zu sagen.

Darüber hinaus kann die Semantik des Gültigkeitsbereichs ausnahmslos global definiert werden, insbesondere, dass die lokalen Variablen so lange gültig sind, wie das {} Sie sind in jedem Fall in definiert. Kleiner Punkt, aber ein Punkt.

Um das zu tun, was Sie wollen, können Sie schließlich einen Satz von Klammern um den try-Haken hinzufügen. Gibt Ihnen den Umfang, den Sie möchten, obwohl dies ein wenig Lesbarkeit kostet, aber nicht zu viel.

{
     String s;
     try
     {
          s = "test";
          //More code
     }
     catch
     {
          Console.WriteLine(s);
     }
}
1
Guvante

Wenn Sie eine lokale Variable deklarieren, wird sie auf dem Stapel abgelegt (bei einigen Typen befindet sich der gesamte Wert des Objekts auf dem Stapel, bei anderen Typen befindet sich nur eine Referenz auf dem Stapel). Wenn es innerhalb eines try-Blocks eine Ausnahme gibt, werden die lokalen Variablen innerhalb des Blocks freigegeben, was bedeutet, dass der Stack wieder in den Zustand versetzt wird, in dem er sich zu Beginn des try-Blocks befand. Dies ist beabsichtigt. Auf diese Weise kann try/catch alle Funktionsaufrufe innerhalb des Blocks zurücksetzen und Ihr System wieder in einen funktionsfähigen Zustand versetzen. Ohne diesen Mechanismus könnten Sie niemals sicher sein, ob eine Ausnahme vorliegt.

Wenn Ihr Fehlerbehandlungscode auf extern deklarierten Variablen basiert, deren Werte im try-Block geändert wurden, erscheint mir dies als schlechtes Design. Was Sie tun, ist im Wesentlichen, absichtlich Ressourcen zu verlieren, um Informationen zu erhalten (in diesem speziellen Fall ist es nicht so schlimm, weil Sie nur Informationen verlieren, aber stellen Sie sich vor, es wäre eine andere Ressource? Sie erschweren sich das Leben in der Zukunft). Ich würde vorschlagen, Ihre Try-Blöcke in kleinere Blöcke aufzuteilen, wenn Sie eine genauere Fehlerbehandlung benötigen.

1
Wedge

Wenn Sie einen Versuch fangen, sollten Sie zum größten Teil wissen, dass Fehler, die es werfen könnte. Diese Ausnahmeklassen enthalten normalerweise alles, was Sie über die Ausnahme benötigen. Wenn nicht, sollten Sie eigene Ausnahmeklassen erstellen und diese Informationen weitergeben. Auf diese Weise müssen Sie die Variablen niemals aus dem try-Block abrufen, da die Ausnahme selbsterklärend ist. Wenn Sie also viel tun müssen, denken Sie an Ihr Design und versuchen Sie zu überlegen, ob es eine andere Möglichkeit gibt, dass Sie entweder kommende Ausnahmen vorhersagen oder die aus den Ausnahmen stammenden Informationen verwenden und dann Ihre eigenen vielleicht erneut werfen können Ausnahme mit mehr Informationen.

1

Mein Gedanke wäre, dass, weil etwas im try-Block die Ausnahme ausgelöst hat, dessen Namespace-Inhalt nicht vertrauenswürdig ist - dh das Verweisen auf den String 's' im catch-Block könnte den Auslöser einer weiteren Ausnahme sein.

0
jpbarto

Nun, wenn es keinen Kompilierungsfehler auslöst und Sie ihn für den Rest der Methode deklarieren könnten, gäbe es keine Möglichkeit, ihn nur im try-Bereich zu deklarieren. Es zwingt Sie, explizit anzugeben, wo die Variable vorhanden sein soll, und keine Annahmen zu treffen.

0
kemiller2002

Wenn wir das Scoping-Block-Problem für einen Moment ignorieren, müsste der Complier in einer Situation, die nicht genau definiert ist, viel härter arbeiten. Obwohl dies nicht unmöglich ist, zwingt der Scoping-Fehler Sie, den Autor des Codes, die Implikation des von Ihnen geschriebenen Codes zu erkennen (dass der String s im catch-Block null sein kann). Wenn Ihr Code legal war, kann im Fall einer OutOfMemory-Ausnahme nicht einmal garantiert werden, dass s ein Speicherslot zugewiesen wurde:

// won't compile!
try
{
    VeryLargeArray v = new VeryLargeArray(TOO_BIG_CONSTANT); // throws OutOfMemoryException
    string s = "Help";
}
catch
{
    Console.WriteLine(s); // whoops!
}

Die CLR (und damit der Compiler) zwingt Sie auch, Variablen zu initialisieren, bevor sie verwendet werden. In dem dargestellten Fangblock kann dies nicht garantiert werden.

Am Ende muss der Compiler also viel arbeiten, was in der Praxis nicht viel bringt und die Leute wahrscheinlich verwirren und zu der Frage führen würde, warum try/catch anders funktioniert.

Zusätzlich zur Konsistenz können der Compiler und die CLR eine größere Garantie für den Zustand einer Variablen innerhalb eines Catch-Blocks bieten, indem sie nichts Besonderes zulassen und die in der Sprache bereits festgelegte Gültigkeitsbereichssemantik einhalten. Dass es existiert und initialisiert wurde.

Beachten Sie, dass die Sprachentwickler gute Arbeit mit anderen Konstrukten wie sing und lock geleistet haben, bei denen das Problem und der Umfang genau definiert sind, sodass Sie klareren Code schreiben können.

z.B. das sing Schlüsselwort mit IDisposable Objekten in:

using(Writer writer = new Writer())
{
    writer.Write("Hello");
}

ist äquivalent zu:

Writer writer = new Writer();
try
{        
    writer.Write("Hello");
}
finally
{
    if( writer != null)
    {
        ((IDisposable)writer).Dispose();
    }
}

Wenn Ihr try/catch/finally schwer zu verstehen ist, versuchen Sie, eine andere Indirektionsebene mit einer Zwischenklasse umzugestalten oder einzuführen, die die Semantik dessen, was Sie erreichen möchten, zusammenfasst. Ohne echten Code zu sehen, ist es schwierig, genauer zu sein.

0
Robert Paulson

Anstelle einer lokalen Variablen kann auch eine öffentliche Eigenschaft deklariert werden. Dies sollte auch einen weiteren potenziellen Fehler einer nicht zugewiesenen Variablen vermeiden. öffentlicher String S {get; einstellen; }

0
usefulBee

Das C # Spec (15.2) besagt "Der Gültigkeitsbereich einer in einem Block deklarierten lokalen Variablen oder Konstanten ist der Block."

(In Ihrem ersten Beispiel ist der try-Block der Block, in dem "s" deklariert ist.)

0
tamberg