在C#和Java(以及可能的其他语言)中,在"try"块中声明的变量不在相应的"catch"或"finally"块的范围内.例如,以下代码无法编译:
try { String s = "test"; // (more code...) } catch { Console.Out.WriteLine(s); //Java fans: think "System.out.println" here instead }
在此代码中,catch块中对s的引用发生编译时错误,因为s仅在try块的范围内.(在Java中,编译错误是"s无法解决";在C#中,它是"当前上下文中不存在名称".)
这个问题的一般解决方案似乎是在try块之前而不是try块中声明变量:
String s; try { s = "test"; // (more code...) } catch { Console.Out.WriteLine(s); //Java fans: think "System.out.println" here instead }
然而,至少在我看来,(1)这感觉就像一个笨重的解决方案,并且(2)它导致变量具有比程序员预期更大的范围(方法的整个剩余部分,而不仅仅是在的try-catch-最后).
我的问题是,这种语言设计决策背后的原理是什么(在Java中,在C#中,和/或在任何其他适用的语言中)?
两件事情:
通常,Java只有两个级别的范围:全局和函数.但是,try/catch是一个例外(没有双关语).抛出异常并且异常对象获得分配给它的变量时,该对象变量仅在"catch"部分中可用,并在catch完成后立即销毁.
(更重要的是).您无法知道try块中抛出异常的位置.它可能在您的变量被声明之前.因此,无法说出catch/finally子句可用的变量.考虑以下情况,其中范围是您建议的:
try { throw new ArgumentException("some operation that throws an exception"); string s = "blah"; } catch (e as ArgumentException) { Console.Out.WriteLine(s); }
这显然是一个问题 - 当你到达异常处理程序时,s将不会被声明.鉴于catchs旨在处理特殊情况并最终必须执行,安全并在编译时声明这个问题远比运行时好.
你怎么能确定你到达了catch区的声明部分?如果实例化抛出异常怎么办?
传统上,在C风格的语言中,花括号内的内容会留在花括号内.我认为在这样的范围内拥有变量的生命周期对大多数程序员来说都是不直观的.你可以通过将try/catch/finally块包含在另一个括号内来实现你想要的.例如
... code ... { string s = "test"; try { // more code } catch(...) { Console.Out.WriteLine(s); } }
编辑:我想每一个规则也有例外.以下是有效的C++:
int f() { return 0; } void main() { int y = 0; if (int x = f()) { cout << x; } else { cout << x; } }
x的范围是conditional,then子句和else子句.
其他人都提出了基础知识 - 块中发生的事情会停留在块中.但就.NET而言,检查编译器认为发生了什么可能会有所帮助.例如,以下try/catch代码(请注意,StreamReader在块之外正确声明):
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(); } } }
这将编译为类似于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
我们看到了什么?MSIL尊重这些块 - 它们本质上是编译C#时生成的底层代码的一部分.范围不仅仅是C#规范中的硬件设置,它也在CLR和CLS规范中.
范围保护您,但您偶尔必须解决它.随着时间的推移,你会习惯它,并开始感到自然.像其他人说的那样,块中发生的事情会停留在该块中.你想分享一些东西吗?你必须走出街区......
无论如何,在C++中,自动变量的范围受到它周围的花括号的限制.为什么有人会期望通过在花括号外面删除try关键字来改变它?
就像ravenspoint指出的那样,每个人都希望变量在定义它们的块中是局部的。try
引入一个块,所以也是如此catch
。
如果希望变量对于try
和都是局部的catch
,请尝试将它们都包含在一个块中:
// here is some code { string s; try { throw new Exception(":(") } catch (Exception e) { Debug.WriteLine(s); } }
简单的答案是C和大多数继承了其语法的语言都是块作用域.这意味着如果变量在一个块中定义,即在{}内,那就是它的范围.
顺便说一句,例外是JavaScript,它具有类似的语法,但是功能范围.在JavaScript中,try块中声明的变量在catch块的范围内,以及其包含函数中的其他位置.