In C # und in Java (und möglicherweise auch in anderen Sprachen) sind in einem "try" -Block deklarierte Variablen in den entsprechenden "catch" - oder "finally" -Blöcken nicht im Gültigkeitsbereich. Der folgende Code wird beispielsweise 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 ein Kompilierungsfehler bei der Referenz auf s im catch-Block auf, da s nur im try-Block im Gültigkeitsbereich liegt. (In Java lautet der Kompilierungsfehler "s kann nicht behoben werden"; in C # heißt es "Der Name 's' existiert im aktuellen Kontext nicht".)
Die allgemeine Lösung für dieses Problem scheint darin zu bestehen, Variablen unmittelbar vor dem try-Block und nicht innerhalb des try-Blocks 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 fühlt sich dies jedoch (1) wie eine klobige Lösung an, und (2) es führt dazu, dass die Variablen einen größeren Umfang haben als vom Programmierer beabsichtigt (der gesamte Rest der Methode, anstatt nur im Kontext der try-catch-finally).
Meine Frage ist, was waren / sind die Gründe für diese Entscheidung zum Sprachdesign (in Java, in C # und / oder in anderen anwendbaren Sprachen)?
In Sprachen im C-Stil bleibt das, was in den geschweiften Klammern passiert, traditionell in den geschweiften Klammern. Ich denke, dass die Lebensdauer einer variablen Ausdehnung über solche Bereiche für die meisten Programmierer nicht intuitiv wäre. Sie können erreichen, was Sie wollen, indem Sie die Blöcke try / catch / finally in eine andere Ebene von Klammern einschließen. z.B
EDIT: Ich denke, jede Regel hat eine Ausnahme. Folgendes ist in C ++ gültig:
Der Geltungsbereich von x ist die Bedingungsklausel, die then-Klausel und die else-Klausel.
quelle
Alle anderen haben die Grundlagen angesprochen - was in einem Block passiert, bleibt in einem Block. Im Fall von .NET kann es jedoch hilfreich sein, zu untersuchen, was der Compiler für möglich hält. Nehmen Sie zum Beispiel den folgenden Try / Catch-Code (beachten Sie, dass der StreamReader außerhalb der Blöcke korrekt deklariert ist):
Dies wird zu etwas ähnlichem wie dem in MSIL kompiliert:
Was sehen wir? MSIL respektiert die Blöcke - sie sind Teil des zugrunde liegenden Codes, der beim Kompilieren Ihres C # generiert wird. Der Bereich 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 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 ...
quelle
In C ++ ist der Umfang einer automatischen Variablen jedenfalls durch die sie umgebenden geschweiften Klammern begrenzt. Warum sollte jemand erwarten, dass dies anders ist, wenn er ein try-Schlüsselwort außerhalb der geschweiften Klammern setzt?
quelle
Wie Ravenspoint hervorhob, erwartet jeder, dass Variablen lokal für den Block sind, in dem er definiert ist. Er
try
führt einen Block ein und tut dies auchcatch
.Wenn Sie lokale Variablen für beide
try
und möchtencatch
, versuchen Sie, beide in einen Block einzuschließen:quelle
Die einfache Antwort lautet, dass C und die meisten Sprachen, die seine Syntax geerbt haben, einen Blockbereich haben. Das heißt, wenn eine Variable in einem Block definiert ist, dh innerhalb von {}, ist dies ihr Gültigkeitsbereich.
Die Ausnahme ist übrigens JavaScript, das eine ähnliche Syntax hat, aber einen Funktionsumfang aufweist. In JavaScript befindet sich eine in einem try-Block deklarierte Variable im catch-Block und überall in der enthaltenen Funktion im Gültigkeitsbereich.
quelle
@burkhard hat die Frage, warum richtig beantwortet wurde, aber als Hinweis wollte ich hinzufügen, dass Ihr empfohlenes Lösungsbeispiel zwar 99,9999 +% der Zeit gut ist, es jedoch keine gute Praxis ist, es weitaus sicherer ist, vor der Verwendung entweder auf Null zu prüfen Innerhalb des try-Blocks wird etwas instanziiert oder die Variable mit etwas initialisiert, anstatt sie nur vor dem try-Block zu deklarieren. Beispielsweise:
Oder:
Dies sollte Skalierbarkeit in der Problemumgehung bieten, sodass Sie auch dann sicher auf die Daten aus Ihrem catch-Block zugreifen können sollten, wenn das, was Sie im try-Block tun, komplexer ist als das Zuweisen einer Zeichenfolge.
quelle
Gemäß dem Abschnitt " Auslösen und Abfangen von Ausnahmen" in Lektion 2 des MCTS Self-Paced Training Kit (Prüfung 70-536): Microsoft® .NET Framework 2.0 - Application Development Foundation liegt der Grund möglicherweise darin, dass die Ausnahme aufgetreten ist vor Variablendeklarationen im try-Block (wie andere bereits bemerkt haben).
Zitat von Seite 25:
"Beachten Sie, dass die StreamReader-Deklaration im vorherigen Beispiel außerhalb des Try-Blocks verschoben wurde. Dies ist erforderlich, da der finally-Block nicht auf Variablen zugreifen kann, die im Try-Block deklariert sind. Dies ist sinnvoll, da je nach dem, wo eine Ausnahme aufgetreten ist, Variablendeklarationen innerhalb des Der Try-Block wurde möglicherweise noch nicht ausgeführt . "
quelle
Die Antwort lautet, wie alle betont haben, so ziemlich "so werden Blöcke definiert".
Es gibt einige Vorschläge, um den Code schöner zu machen. Siehe ARM
Schließungen sollen dies ebenfalls ansprechen.
UPDATE: ARM ist in Java 7 implementiert. Http://download.java.net/jdk7/docs/technotes/guides/language/try-with-resources.html
quelle
Ihre Lösung ist genau das, was Sie tun sollten. Sie können nicht sicher sein, ob Ihre Deklaration überhaupt im try-Block erreicht wurde, was zu einer weiteren Ausnahme im catch-Block führen würde.
Es muss einfach als separater Bereich funktionieren.
quelle
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.
Der String würde niemals deklariert werden, daher kann er nicht abhängig gemacht werden.
quelle
Weil der try-Block und der catch-Block zwei verschiedene Blöcke sind.
Würden Sie im folgenden Code erwarten, dass in Block A definierte s in Block B sichtbar sind?
quelle
Während es in Ihrem Beispiel seltsam ist, dass es nicht funktioniert, nehmen Sie dieses ähnliche:
Dies würde dazu führen, dass der Fang eine Nullreferenzausnahme auslöst, wenn Code 1 fehlerhaft ist. Obwohl 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 gemeinsamer Semantik wäre es.
Auch dies könnte theoretisch behoben werden, indem nur getrennte Definitionen (
String s; s = "1|2";
) oder andere Bedingungen zugelassen werden, aber es ist im Allgemeinen einfacher, einfach Nein zu sagen.Darüber hinaus kann die Semantik des Gültigkeitsbereichs ausnahmslos global definiert werden. Insbesondere halten die Einheimischen
{}
in allen Fällen so lange, wie sie definiert sind. Kleiner Punkt, aber ein Punkt.Um das zu tun, was Sie wollen, können Sie schließlich eine Reihe von Klammern um den Try-Catch setzen. Gibt Ihnen den gewünschten Umfang, obwohl dies auf Kosten einer geringen Lesbarkeit geht, aber nicht zu viel.
quelle
In dem von Ihnen angegebenen Beispiel kann das Initialisieren von s keine Ausnahme auslösen. Sie würden also denken, dass der Anwendungsbereich möglicherweise erweitert werden könnte.
Im Allgemeinen können Initialisiererausdrücke jedoch Ausnahmen auslösen. Es wäre nicht sinnvoll, wenn eine Variable, deren Initialisierer eine Ausnahme ausgelöst hat (oder die nach einer anderen Variablen deklariert wurde, in der dies geschehen ist), im Bereich für catch / finally liegt.
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: Variable Bereiche folgen Blöcken.
Wenn Sie möchten, dass sich eine Variable im Bereich von try / catch / finally befindet, aber nirgendwo anders, dann wickeln Sie das Ganze in einen anderen Satz von Klammern (einen bloßen Block) und deklarieren Sie die Variable vor dem Versuch.
quelle
Ein Grund dafür, dass sie nicht im selben Bereich liegen, ist, dass Sie an jedem Punkt des try-Blocks die Ausnahme ausgelöst haben können. Wenn sie sich im selben Bereich befinden, ist es eine Katastrophe beim Warten, denn je nachdem, wo die Ausnahme ausgelöst wurde, könnte sie noch mehrdeutiger sein.
Zumindest wenn es außerhalb des try-Blocks deklariert ist, wissen Sie sicher, wie hoch die Variable mindestens sein kann, wenn eine Ausnahme ausgelöst wird. Der Wert der Variablen vor dem try-Block.
quelle
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 innerhalb eines try-Blocks eine Ausnahme vorliegt, werden die lokalen Variablen innerhalb des Blocks freigegeben, was bedeutet, dass der Stapel wieder in den Zustand "abgewickelt" 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 beenden und Ihr System wieder in einen Funktionszustand versetzen. Ohne diesen Mechanismus könnten Sie niemals sicher sein, ob etwas passiert, wenn eine Ausnahme auftritt.
Es scheint mir ein schlechtes Design zu sein, wenn sich Ihr Fehlerbehandlungscode auf extern deklarierte Variablen stützt, deren Werte innerhalb des try-Blocks geändert wurden. 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 machen sich das Leben in der Welt nur schwerer Zukunft). Ich würde vorschlagen, Ihre Try-Blöcke in kleinere Blöcke aufzuteilen, wenn Sie mehr Granularität bei der Fehlerbehandlung benötigen.
quelle
Wenn Sie einen Versuch fangen, sollten Sie größtenteils wissen, welche Fehler es werfen könnte. Diese Ausnahmeklassen sagen 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 dies also viel tun müssen, denken Sie über Ihr Design nach und versuchen Sie zu überlegen, ob es eine andere Möglichkeit gibt, entweder das Kommen von Ausnahmen vorherzusagen oder die aus den Ausnahmen kommenden Informationen zu verwenden und dann möglicherweise Ihre eigenen erneut zu werfen Ausnahme mit mehr Informationen.
quelle
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 Geltungsbereich 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 obj.Dispose () im finally-Block aufrufen. Oder Sie können das Schlüsselwort using verwenden, das am Ende des Codeabschnitts automatisch Dispose aufruft.
quelle
In Python sind sie in den catch / finally-Blöcken sichtbar, wenn die sie deklarierende Zeile nicht geworfen hat.
quelle
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 zustande gekommen.
quelle
In der C # -Spezifikation (15.2) heißt es: "Der Gültigkeitsbereich einer in einem Block deklarierten lokalen Variablen oder Konstante ist der Block."
(In Ihrem ersten Beispiel ist der try-Block der Block, in dem "s" deklariert ist.)
quelle
Mein Gedanke wäre, dass, weil etwas im try-Block die Ausnahme ausgelöst hat, dessen Namespace-Inhalt nicht vertrauenswürdig ist. Wenn Sie also auf die Zeichenfolgen im catch-Block verweisen, kann dies zu einer weiteren Ausnahme führen.
quelle
Wenn es keinen Kompilierungsfehler auslöst und Sie ihn für den Rest der Methode deklarieren können, gibt es keine Möglichkeit, ihn nur im Versuchsbereich zu deklarieren. Es zwingt Sie dazu, explizit anzugeben, wo die Variable existieren soll, und keine Annahmen zu treffen.
quelle
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. Dies ist zwar nicht unmöglich, aber der Scoping-Fehler zwingt Sie, den Autor des Codes, auch, die Implikation des von Ihnen geschriebenen Codes zu erkennen (dass die Zeichenfolge s im catch-Block null sein kann). Wenn Ihr Code legal war, wird s im Fall einer OutOfMemory-Ausnahme nicht einmal garantiert, dass s ein Speicherplatz zugewiesen wird:
Die CLR (und damit der Compiler) zwingen Sie außerdem, Variablen zu initialisieren, bevor sie verwendet werden. In dem dargestellten Fangblock kann dies nicht garantiert werden.
Am Ende muss der Compiler viel arbeiten, was in der Praxis nicht viel bringt und die Leute wahrscheinlich verwirren und sie dazu bringen würde, zu fragen, warum Try / Catch anders funktioniert.
Zusätzlich zur Konsistenz können der Compiler und die CLR eine bessere Garantie für den Status einer Variablen innerhalb eines Catch-Blocks bieten, indem sie nichts Besonderes zulassen und die bereits etablierte Scoping-Semantik einhalten, die in der gesamten Sprache verwendet wird. Dass es existiert und initialisiert wurde.
Beachten Sie, dass die Sprachdesigner gute Arbeit mit anderen Konstrukten wie Verwenden und Sperren geleistet haben, bei denen das Problem und der Umfang genau definiert sind, sodass Sie klareren Code schreiben können.
zB das Schlüsselwort using mit IDisposable- Objekten in:
ist äquivalent zu:
Wenn Ihr Versuch / Fang / Endlich 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.
quelle
Anstelle einer lokalen Variablen könnte eine öffentliche Eigenschaft deklariert werden. Dies sollte auch einen weiteren möglichen Fehler einer nicht zugewiesenen Variablen vermeiden. öffentlicher String S {get; einstellen; }}
quelle
Wenn die Zuweisungsoperation fehlschlägt, hat Ihre catch-Anweisung einen Nullverweis zurück auf die nicht zugewiesene Variable.
quelle
C # 3.0:
quelle