为什么在“catch”或“finally”的范围内“try”中没有声明变量?

问题

在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#中,和/或在任何其他适用的语言中)?


#1 热门回答(142 赞)

两件事情:

  • 通常,Java只有两个级别的范围:全局和功能。但是,try / catch是一个例外(没有双关语)。抛出异常并且异常对象获得分配给它的变量时,该对象变量仅在"catch"部分中可用,并在catch完成后立即销毁。
  • (更重要的是)。你无法知道try块中抛出异常的位置。它可能是在你的变量被声明之前。因此,不可能说出catch / finally子句可用的变量。考虑以下情况,其中范围是你建议的:
    尝试
    {
        抛出新的ArgumentException("抛出异常的一些操作");
        string s ="blah";
    }
    catch(e as ArgumentException)
    {
        Console.Out.WriteLine(一个或多个);
    }

这显然是一个问题 - 当你到达异常处理程序时,s将不会被声明。鉴于捕获旨在处理异常情况并最终导致执行,安全并在编译时声明这个问题远比运行时好。


#2 热门回答(52 赞)

你怎么能确定你到达了拦截区的声明部分?如果实例化抛出异常怎么办?


#3 热门回答(17 赞)

传统上,在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子句。