Warum Variablen werden nicht in „versuchen“ im Rahmen in „fangen“ oder „endlich“ erklärt?

StackOverflow https://stackoverflow.com/questions/94977

Frage

In C # und in Java (und möglicherweise auch anderen Sprachen), deklariert Variablen in einem „Try“ Block ist in ihrem Umfang nicht in der entsprechenden „fangen“ oder „endlich“ blockiert. Zum Beispiel ist der folgende Code nicht kompiliert werden:

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

In diesem Code ein Fehler bei der Kompilierung tritt auf dem Verweise auf s im catch-Block, da s nur in ihrem Umfang im try-Block ist. (In Java, der Compiler-Fehler ist "s nicht aufgelöst werden kann."; In C #, es ist "Der Name 's' existiert nicht im aktuellen Kontext")

Die allgemeine Lösung für dieses Problem scheint stattdessen zu sein, um Variablen kurz vor dem try-Block zu deklarieren, anstatt innerhalb des Try-Block:

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

jedoch zumindest für mich, (1) das fühlt sich an wie eine klobig Lösung, und (2) führt dies zu den Variablen einen größeren Umfang als der Programmierer beabsichtigt (den gesamten Rest des Verfahrens sind, statt nur in dem Rahmen des try-catch-finally).

Meine Frage ist, was waren / sind die Gründe (n) hinter dieser Sprache Design-Entscheidung (in Java, C # und / oder in anderen anwendbaren Sprachen)?

War es hilfreich?

Lösung

Zwei Dinge:

  1. Im Allgemeinen Java hat nur 2 Ebene des Anwendungsbereich: global und Funktion. Aber try / catch ist eine Ausnahme (kein Wortspiel beabsichtigt). Wenn eine Ausnahme ausgelöst wird und das Ausnahmeobjekt wird eine Variable zugeordnet ist, ist, dass Objektvariable innerhalb des „fängt“ Abschnitt nur verfügbar und wird, sobald der Haken abgeschlossen zerstört.

  2. (und was noch wichtiger ist). Sie können nicht wissen, wo im try-Block die Ausnahme ausgelöst wurde. Es kann gewesen sein, bevor Sie Ihre Variable deklariert wurde. Deshalb ist es unmöglich zu sagen, welche Variablen für die catch / finally-Klausel zur Verfügung stehen werden. Betrachten Sie den folgenden Fall, wo Scoping ist, wie Sie vorgeschlagen:

    
    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 Exception-Handler zu erreichen, wird s nicht erklärt wurde. Da die Fänge sollen außergewöhnliche Umstände behandeln und finallys muss ausführen, wobei sicher und erklärt dies ein Problem bei der Kompilierung ist weit besser als zur Laufzeit.

Andere Tipps

Wie können Sie sicher sein, dass Sie den Deklarationsteil in Ihrem Catch-Block erreicht? Was passiert, wenn die Instantiierung wirft die Ausnahme?

Traditionell in C-Stil Sprachen, was in geschweiften Klammern passiert, bleibt in geschweiften Klammern. Ich denke, dass so über Bereiche, die Lebensdauer einer variablen Strecke mit den meisten Programmierer nicht intuitiv sein würde. Sie können erreichen, was Sie wollen, indem Sie die try / catch / finally Blöcke innerhalb einer anderen Ebene von Klammern umschließt. z.

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

EDIT: Ich denke, jede Regel nicht hat eine Ausnahme. Folgendes gilt C ++:

int f() { return 0; }

void main() 
{
    int y = 0;

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

Der Umfang von x ist die bedingte, die dann Klausel und die else-Klausel.

Jeder andere hat die Grundlagen gebracht - was in einem Block Aufenthalt in einem Block passiert. Aber im Fall von .NET, kann es hilfreich sein, zu untersuchen, was der Compiler denkt geschieht. Nehmen wir zum Beispiel die folgende try / catch-Code (beachten Sie, dass die Stream deklariert ist, richtig, außerhalb der Blöcke):

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 kompiliert, um etwas ähnlich der folgenden in MSIL:

.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 untrennbar Teil des zugrunde liegenden Code generiert, wenn Sie Ihre C # kompilieren. Der Umfang ist nicht nur schwer festgelegt in dem C # spec, ist es in den CLR und CLS als auch spec.

Der Umfang schützt Sie, aber Sie müssen gelegentlich um es zu arbeiten. Im Laufe der Zeit gewöhnt man sich daran gewöhnt, und es beginnt natürlich zu fühlen. Wie alle anderen gesagt, was passiert in einem Block bleibt in diesem Block. Sie wollen etwas teilen? Sie haben außerhalb der Blöcke gehen ...

In C ++ auf jeden Fall, der Umfang eines automatischen Variable wird durch die geschweiften Klammern beschränkt, die sie umgeben. Warum sollte jemand erwarten, dass dies anders zu sein von plunking einen Versuch Stichwort außerhalb der geschweiften Klammern nach unten?

Wie Ravens wies darauf hin, jeder erwartet Variablen auf den Block lokal sein sie in definiert sind. try einen Block führt und so catch ist.

Wenn Sie lokale Variablen sowohl try und catch wollen, versuchen Sie umschließt sowohl in einem Block:

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

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

Die einfache Antwort ist, dass C und die meisten der Sprachen, die ihre Syntax sind Block scoped geerbt haben. Das bedeutet, dass, wenn eine Variable in einem Block definiert wird, das heißt, innerhalb {}, das heißt in ihrem Umfang.

Die Ausnahme, übrigens, ist JavaScript, die eine ähnliche Syntax, ist aber Funktion scoped. In JavaScript, eine Variable in einem Try-Block deklariert ist in ihrem Umfang im catch-Block und überall sonst in seiner Funktion enthält.

@burkhard hat sich die Frage, warum richtig beantwortet, aber als Notiz ich hinzufügen wollte, während die empfohlene Lösung Beispiel gut 99,9999 +% der Zeit ist, ist es nicht empfehlenswert, es ist viel sicherer, entweder zu prüfen null, bevor etwas instanziiert innerhalb des try-Block, oder initialisieren die Variable auf etwas, anstatt nur erklärt er vor dem try-Block verwenden. Zum Beispiel:

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 Skalierbarkeit in der Abhilfe schaffen, so dass auch wenn das, was Sie im try-Block tun komplexer ist, als ein Zeichenfolge zuweisen, sollten Sie in der Lage sein, sicher die Daten von Ihrem catch-Block zu gelangen.

Die Antwort, wie jeder bemerkt hat, ist so ziemlich „das ist, wie Blöcke definiert“.

Es gibt einige Vorschläge den Code bisschen besser zu machen. Siehe ARM

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

Closures sollen, dies auch adressieren.

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

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

Nach dem Abschnitt mit dem Titel "How to werfen und fangen Ausnahmen" in Lektion 2 von MCTS Paced Training Kit (Prüfung 70-536): Microsoft .NET Framework 2.0-Application Development Foundation , der Grund dafür ist, dass die Ausnahme vor Variablendeklarationen im try-Block aufgetreten ist (wie andere erwähnt haben bereits).

Zitat von Seite 25:

"Beachten Sie, dass die Stream Erklärung außerhalb des Try-Block in dem vorhergehenden Beispiel verschoben wurde. Dies ist notwendig, weil der finally-Block nicht Variablen zugreifen können, die innerhalb des Try-Block deklariert werden. Das macht Sinn, weil je nach wo eine Ausnahme aufgetreten ist, könnte Variablendeklarationen innerhalb des Try-Block noch nicht ausgeführt wurden . "

Sie Lösung ist genau das, was Sie tun sollten. Sie können nicht sicher sein, dass Ihre Erklärung auch im try-Block erreicht wurde, die in einer anderen Ausnahme im catch-Block führen würde.

Es muss einfach als separate Bereiche arbeiten.

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

Die Variablen sind Blockebene und beschränkt auf, die versuchen, oder Catch-Block. Ähnliche eine Variable in einer if-Anweisung zu definieren. Denken Sie an diese Situation.

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

Der String würde nie deklariert werden, so kann es nicht auf sie verlassen werden.

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

Im folgenden Code, würden Sie s erwarten in Block A definiert in Block B sichtbar sein?

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

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

In dem speziellen Beispiel, Sie haben angegeben, in der Initialisierungsphase s nicht eine Ausnahme auslösen kann. So würde man denken, dass vielleicht in ihrem Umfang erweitert werden kann.

Aber im Allgemeinen kann initialiser Ausdrücke Ausnahmen werfen. Es wäre nicht sinnvoll, für eine Variable, deren initialiser warf eine Ausnahme machen (oder die nach einer anderen Variable deklariert wurde, in dem, was geschehen) für catch / finally in Rahmen zu sein.

Auch die Lesbarkeit des Codes würde leiden. Die Regel in C (und Sprachen, die sie folgen, darunter C ++, Java und C #) ist einfach:. Variable Bereiche folgen Blöcke

Wenn Sie eine Variable im Rahmen zu sein für try / catch / finally aber nirgendwo sonst, dann wickeln Sie das Ganze in einem anderen Satz von Klammern (ein nackter Block) und deklarieren Sie die Variable vor dem Versuch.

Ein Teil des Grundes, warum sie nicht in gleichem Umfang ist, weil an jedem Punkt des try-Blockes, können Sie die Ausnahme ausgelöst haben. Wenn sie in dem gleichen Umfang, seine eine Katastrophe in Warte sind, denn je nachdem, wo die Ausnahme ausgelöst wurde, kann es noch nicht eindeutig sein.

Zumindest, wenn seine Außenseite der try-Blöcke erklären, wissen Sie genau, was die Variable bei minimal sein könnte, wenn eine Ausnahme ausgelöst wird; Der Wert der Variablen vor dem try-Block.

Wenn Sie eine lokale Variable deklarieren sie auf den Stapel gelegt wird (für einige Typen der gesamte Wert des Objekts für andere Typen auf dem Stapel, sei nur eine Referenz auf dem Stapel sein wird). Wenn es eine Ausnahme in einem try-Block werden die lokalen Variablen in dem Block befreit, die die Stapel Mittel „abgewickelte“ zurück zum Zustand es zu Beginn des try-Blockes war. Das ist von Entwurf. Es ist, wie der try / catch Lage ist, Anrufe innerhalb des Blocks aus allen der Funktion zu sichern und setzt Ihr System wieder in einen funktionsfähigen Zustand. Ohne diesen Mechanismus könnten Sie nie von dem Zustand etwas sicher sein, wenn eine Ausnahme auftritt.

Ihre Fehlerbehandlungscode verlassen sich auf extern deklarierten Variablen, die ihre Werte geändert haben innerhalb der try-Block wie schlechtes Design zu mir scheint. Was Sie tun, ist im wesentlichen Ressourcen absichtlich, um undichte Informationen zu erhalten (in diesem speziellen Fall ist es nicht so schlimm, weil Sie nur undichte Informationen sind, aber sich vorstellen, wenn es eine andere Ressource waren? Sie machen einfach das Leben auf sich selbst härter in die Zukunft). Ich würde vorschlagen, Ihre try-Blöcke in kleinere Stücke zerbrechen, wenn Sie mehr Granularität bei der Fehlerbehandlung erforderlich ist.

Wenn Sie einen Versuch fangen haben, können Sie bei den meisten Fällen sollten wissen, dass Fehler, dass es vielleicht werfen. Theese Exception-Klassen sagen normaly alles, was Sie über die Ausnahme benötigen. Wenn nicht, sollten Sie machen Sie eigene Ausnahmeklassen sind und entlang dieser Informationen übergeben. Auf diese Weise werden Sie nie die Variablen aus dem Inneren des Try-Block erhalten müssen, weil die Ausnahme selbsterklärend ist. Also, wenn Sie benötigen diese eine Menge zu tun, über Sie denken Design sind, und versuchen zu denken, wenn es eine andere Möglichkeit ist, dass Sie entweder Ausnahmen vorhersagen comming, oder die Informationen, die von den Ausnahmen comming, und dann vielleicht rethrow Ihre eigene Ausnahme mit weiteren Informationen.

Wie bereits von anderen Benutzern, die geschweiften Klammern definieren Umfang in so ziemlich jede C-Stil-Sprache, die ich kenne, hingewiesen.

Wenn es eine einfache Variable, warum dann ist es egal, wie lange es in Umfang sein wird? Es ist nicht so große Sache.

in C #, wenn es sich um eine komplexe Variable ist, werden Sie IDisposable implementieren. Sie können dann entweder verwenden try / catch / finally und rufen obj.Dispose () in der schließlich blockieren. Oder Sie können die Verwendung von Keyword verwenden, die automatisch die Entsorgung am Ende des Codeabschnitts nennen.

In Python sind sie sichtbar in der catch / finally Blöcken, wenn die Leitung sie erklärt nicht werfen.

Was passiert, wenn die Ausnahme in einigen Code ausgelöst wird, die oberhalb der Deklaration der Variablen ist. Was bedeutet, wurde die Erklärung selbst in diesem Fall nicht passiert.

try {

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

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

Während in Ihrem Beispiel es seltsam ist, dass es nicht funktioniert, nehmen diese ähnlich einer:

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

Dies würde den Fang verursacht eine NULL-Verweis Ausnahme zu werfen, wenn Code 1 bricht. Jetzt, während die Semantik der try / catch ist ziemlich gut verstanden, wäre dies ein lästige Ecke Fall sein, da s mit einem Anfangswert definiert ist, so sollte es theoretisch nie Null sein, aber im Rahmen der geteilten Semantik, wäre es.

Auch in diesem in der Theorie indem nur getrennt Definitionen (String s; s = "1|2";) festgelegt werden könnte, oder eine andere Reihe von Bedingungen, aber es ist in der Regel einfacher, nur nicht zu sagen.

Darüber hinaus ermöglicht es die Semantik des Umfangs ausnahmslos global definiert werden, und zwar Einheimischen, solange die {} dauern sie in definiert sind, in allen Fällen. Minor Punkt, aber ein Punkt.

Schließlich, um zu tun, was Sie wollen, können Sie eine Reihe von Klammern um den Try-Catch hinzufügen. Gibt Ihnen den Umfang Sie wollen, auch wenn es auf Kosten eines kleinen Lesbarkeit kommen wird, aber nicht zu viel.

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

Mein Gedanke wäre, dass da etwas im try-Block die Ausnahme dessen Namensraum Inhalt ausgelöst kann nicht vertraut werden. - dh Referenzierung den String ‚s‘ im catch-Block den Ball noch eine weitere Ausnahme dazu führen könnte,

Nun, wenn es nicht einen Compiler-Fehler nicht werfen, und man konnte es für den Rest des Verfahrens erklärt, dann gäbe es keine Möglichkeit, nur um es zu erklären nur innerhalb try Umfangs. Es zwingt Sie explizit zu sein, wo die Variable soll auf existiert und Annahmen nicht.

Wenn wir das Scoping-Block Problem für einen Moment ignorieren, würden die complier haben viel schwieriger in einer Situation zu arbeiten, die nicht gut definiert sind. Zwar ist dies nicht unmöglich ist, auch der Scoping Fehler zwingt Sie, den Autor des Codes, die Implikation des Codes zu erkennen, Sie schreiben (dass die Zeichenfolge s im catch-Block null sein kann). Wenn Ihr Code legal war, im Fall einer OutOfMemory Ausnahme ist s nicht einmal garantiert, um einen Speicher-Slot zugewiesen werden:

// 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 Compiler) auch Sie zwingen, Variablen zu initialisieren, bevor sie verwendet werden. In dem catch-Block dargestellt kann das nicht garantieren.

So wir am Ende mit der Compiler eine Menge Arbeit zu tun haben, die in der Praxis nicht viel Nutzen bereitstellt und würde wahrscheinlich die Menschen verwirren und sie führen zu fragen, warum try / catch funktioniert anders.

Neben der Konsistenz, durch nichts so dass Phantasie und die Einhaltung der bereits etablierten Scoping-Semantik in der gesamten Sprache verwendet wird, der Compiler und CLR ist in der Lage eine größere Garantie für den Zustand einer Variable in einem catch-Block zu schaffen. Dass es existiert und initialisiert wurde.

Beachten Sie, dass die Sprache Designer einen guten Job mit anderen Konstrukten wie getan haben mit und Sperre , wo das Problem und Umfang gut definiert sind, die Sie klareren Code schreiben kann .

z. die mit Stichwort mit IDisposable Objekte in:

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

entspricht:

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

Wenn Ihr try / catch / finally schwer zu verstehen ist, versucht Refactoring oder eine andere Schicht von Dereferenzierung mit einer Zwischenklasse einzuführen, die die Semantik von dem, was kapseln Sie versuchen zu erreichen. Ohne echten Code zu sehen, ist es schwer, um genauer zu sein.

Anstelle einer lokalen Variablen, könnte ein öffentliches Eigentum deklariert werden; Dies sollte auch eine andere mögliche Fehler einer nicht zugeordneten Variable vermeiden. public string S {get; einstellen; }

Die C # Spec (15.2) heißt es: „Die Rahmen einer lokalen Variable oder konstante in einem Block ist der Block deklariert. "

(im ersten Beispiel des try-Block ist der Block, in den "s" deklariert wird)

Wenn die Zuweisung Vorgang fehlschlägt Ihre catch-Anweisung einen NULL-Verweis zurück auf die nicht zugewiesene Variable hat.

C # 3.0:

string html = new Func<string>(() =>
{
    string webpage;

    try
    {
        using(WebClient downloader = new WebClient())
        {
            webpage = downloader.DownloadString(url);
        }
    }
    catch(WebException)
    {
        Console.WriteLine("Download failed.");  
    }

    return webpage;
})();
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top