为什么不在“try”中声明变量? 在“catch”的范围内 或“终于”?

发布于 2024-07-05 18:36:01 字数 740 浏览 6 评论 0原文

在 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# 中,则是“当前上下文中不存在名称 s”。)

此问题的一般解决方案似乎是在变量之前声明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-finally 的上下文中)。

我的问题是,这个语言设计决策背后的基本原理是什么(在 Java、C# 和/或任何其他适用的语言中)?

In C# and in Java (and possibly other languages as well), variables declared in a "try" block are not in scope in the corresponding "catch" or "finally" blocks. For example, the following code does not compile:

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

In this code, a compile-time error occurs on the reference to s in the catch block, because s is only in scope in the try block. (In Java, the compile error is "s cannot be resolved"; in C#, it's "The name 's' does not exist in the current context".)

The general solution to this issue seems to be to instead declare variables just before the try block, instead of within the try block:

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

However, at least to me, (1) this feels like a clunky solution, and (2) it results in the variables having a larger scope than the programmer intended (the entire remainder of the method, instead of only in the context of the try-catch-finally).

My question is, what were/are the rationale(s) behind this language design decision (in Java, in C#, and/or in any other applicable languages)?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(28

萌梦深 2024-07-12 18:36:01

简单的答案是,C 和大多数继承其语法的语言都是块作用域的。 这意味着如果一个变量是在一个块中定义的,即在 { } 内,那么这就是它的作用域。

顺便说一句,JavaScript 是个例外,它具有类似的语法,但具有函数作用域。 在 JavaScript 中,try 块中声明的变量位于 catch 块的作用域内,并且位于其包含函数的其他任何位置。

The simple answer is that C and most of the languages that have inherited its syntax are block scoped. That means that if a variable is defined in one block, i.e., inside { }, that is its scope.

The exception, by the way, is JavaScript, which has a similar syntax, but is function scoped. In JavaScript, a variable declared in a try block is in scope in the catch block, and everywhere else in its containing function.

红ご颜醉 2024-07-12 18:36:01

就像 ravenspoint 指出的那样,每个人都希望变量是定义它们的块的本地变量。try 引入了一个块,catch 也是如此。

如果您想要 trycatch 都具有本地变量,请尝试将两者包含在一个块中:

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

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

Like ravenspoint pointed out, everyone expects variables to be local to the block they are defined in. try introduces a block and so does catch.

If you want variables local to both try and catch, try enclosing both in a block:

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

        throw new Exception(":(")
    }
    catch (Exception e)
    {
        Debug.WriteLine(s);
    }
}
残花月 2024-07-12 18:36:01

当您声明局部变量时,它被放置在堆栈上(对于某些类型,对象的整个值将位于堆栈上,对于其他类型,只有引用将位于堆栈上)。 当 try 块内出现异常时,该块内的局部变量将被释放,这意味着堆栈将“展开”回到 try 块开始时的状态。 这是设计使然。 这就是 try / catch 能够退出块内所有函数调用并将系统恢复到功能状态的方式。 如果没有这种机制,当异常发生时,您将永远无法确定任何事物的状态。

让错误处理代码依赖于外部声明的变量,这些变量的值在 try 块内更改,对我来说似乎是糟糕的设计。 您所做的本质上是为了获取信息而故意泄漏资源(在这种特殊情况下,这并没有那么糟糕,因为您只是泄漏了信息,但想象一下,如果它是其他资源?您只是让自己的生活变得更加艰难)未来)。 如果您需要更精细的错误处理,我建议将您的 try 块分解为更小的块。

When you declare a local variable it is placed on the stack (for some types the entire value of the object will be on the stack, for other types only a reference will be on the stack). When there is an exception inside a try block, the local variables within the block are freed, which means the stack is "unwound" back to the state it was at at the beginning of the try block. This is by design. It's how the try / catch is able to back out of all of the function calls within the block and puts your system back into a functional state. Without this mechanism you could never be sure of the state of anything when an exception occurs.

Having your error handling code rely on externally declared variables which have their values changed inside the try block seems like bad design to me. What you are doing is essentially leaking resources intentionally in order to gain information (in this particular case it's not so bad because you are only leaking information, but imagine if it were some other resource? you're just making life harder on yourself in the future). I would suggest breaking up your try blocks into smaller chunks if you require more granularity in error handling.

牵你的手,一向走下去 2024-07-12 18:36:01

因为try块和catch块是2个不同的块。

在下面的代码中,您是否期望块 A 中定义的 s 在块 B 中可见?

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

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

Because the try block and the catch block are 2 different blocks.

In the following code, would you expect s defined in block A be visible in block B?

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

{ // block B
  Console.Out.WriteLine(s); // or printf or whatever
}
Saygoodbye 2024-07-12 18:36:01

在您给出的具体示例中,初始化不能引发异常。 所以你会认为它的范围也许可以扩大。

但一般来说,初始化表达式可能会引发异常。 对于一个初始化程序引发异常的变量(或者在发生异常的另一个变量之后声明的变量)来说,在 catch/finally 的范围内是没有意义的。

此外,代码的可读性也会受到影响。 C(以及遵循该规则的语言,包括 C++、Java 和 C#)中的规则很简单:变量作用域遵循块。

如果您希望变量位于 try/catch/finally 的范围内,但不在其他地方,则将整个变量包装在另一组大括号(裸块)中,并在 try 之前声明该变量。

In the specific example you've given, initialising s can't throw an exception. So you'd think that maybe its scope could be extended.

But in general, initialiser expressions can throw exceptions. It wouldn't make sense for a variable whose initialiser threw an exception (or which was declared after another variable where that happened) to be in scope for catch/finally.

Also, code readability would suffer. The rule in C (and languages which follow it, including C++, Java and C#) is simple: variable scopes follow blocks.

If you want a variable to be in scope for try/catch/finally but nowhere else, then wrap the whole thing in another set of braces (a bare block) and declare the variable before the try.

铃予 2024-07-12 18:36:01

这些变量是块级的,并且仅限于 Try 或 Catch 块。 类似于在 if 语句中定义变量。 想想这种情况。

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

String 永远不会被声明,因此不能依赖它。

The variables are block level and restricted to that Try or Catch block. Similar to defining a variable in an if statement. Think of this situation.

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

The String would never be declared, so it can't be depended upon.

浅忆流年 2024-07-12 18:36:01

您的解决方案正是您应该做的。 您甚至无法确定您的声明是否已在 try 块中达到,这将导致 catch 块中出现另一个异常。

它必须作为单独的范围工作。

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

You solution is exactly what you should do. You can't be sure that your declaration was even reached in the try block, which would result in another exception in the catch block.

It simply must work as separate scopes.

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
怕倦 2024-07-12 18:36:01

正如每个人都指出的那样,答案几乎是“这就是块的定义方式”。

有一些建议可以使代码更漂亮。 请参阅 ARM

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

闭包也应该解决这个问题。

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

更新: ARM 在 Java 7 中实现。http://download.java.net/jdk7/docs/technotes/guides/language/try-with-resources.html

The answer, as everyone has pointed out, is pretty much "that's how blocks are defined".

There are some proposals to make the code prettier. See ARM

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

Closures are supposed to address this as well.

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

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

杀お生予夺 2024-07-12 18:36:01

根据《MCTS 自定进度培训套件(考试 70-536):Microsoft® .NE​​T Framework 2.0—应用程序开发基础》第 2 课“如何抛出和捕获异常”一节,原因是是异常可能发生在 try 块中的变量声明之前(正如其他人已经指出的那样)。

引用第 25 页:

“请注意,在前面的示例中,StreamReader 声明已移至 Try 块之外。这是必要的,因为 Final 块无法访问 Try 块内声明的变量。这是有道理的,因为根据异常发生的位置,Try 块中的变量声明可能尚未执行。”

According to the section titled "How to Throw and Catch Exceptions" in Lesson 2 of MCTS Self-Paced Training Kit (Exam 70-536): Microsoft® .NET Framework 2.0—Application Development Foundation, the reason is that the exception may have occurred before variable declarations in the try block (as others have noted already).

Quote from page 25:

"Notice that the StreamReader declaration was moved outside the Try block in the preceding example. This is necessary because the Finally block cannot access variables that are declared within the Try block. This makes sense because depending on where an exception occurred, variable declarations within the Try block might not yet have been executed."

梦里的微风 2024-07-12 18:36:01

@burkhard 有一个关于为什么正确回答的问题,但我想补充一点,虽然你推荐的解决方案示例在 99.9999+% 的时间内都很好,但这不是一个好的做法,在使用之前检查 null 会更安全在 try 块内实例化某些内容,或者将变量初始化为某些内容,而不是仅在 try 块之前声明它。 例如:

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

或者:

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

这应该提供解决方法的可扩展性,这样即使您在 try 块中执行的操作比分配字符串更复杂,您也应该能够安全地从 catch 块访问数据。

@burkhard has the question as to why answered properly, but as a note I wanted to add, while your recommended solution example is good 99.9999+% of time, it is not good practice, it is far safer to either check for null before using something instantiate within the try block, or initialize the variable to something instead of just declaring it before the try block. For example:

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

Or:

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

This should provide scalability in the workaround, so that even when what you're doing in the try block is more complex than assigning a string, you should be able to safely access the data from your catch block.

忆沫 2024-07-12 18:36:01

虽然在您的示例中,奇怪的是它不起作用,但采用类似的示例:

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

如果代码 1 损坏,这将导致 catch 抛出空引用异常。 现在,虽然 try/catch 的语义很好理解,但这将是一个恼人的极端情况,因为 s 是用初始值定义的,所以理论上它不应该为 null,但在共享语义下,它会是 null。

同样,理论上,这可以通过仅允许单独的定义 (String s; s = "1|2";) 或其他一些条件集来解决,但通常直接说“不”更容易。

此外,它允许无一例外地在全局范围内定义作用域的语义,具体来说,在所有情况下,局部变量只要定义在其中的 {} 就持续存在。 虽然是次要的一点,但也是一点。

最后,为了执行您想要的操作,您可以在 try catch 周围添加一组括号。 给你你想要的范围,虽然它确实以一点可读性为代价,但不会太多。

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

While in your example it is weird that it does not work, take this similar one:

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

This would cause the catch to throw a null reference exception if Code 1 broke. Now while the semantics of try/catch are pretty well understood, this would be an annoying corner case, since s is defined with an initial value, so it should in theory never be null, but under shared semantics, it would be.

Again this could in theory be fixed by only allowing separated definitions (String s; s = "1|2";), or some other set of conditions, but it is generally easier to just say no.

Additionally, it allows the semantics of scope to be defined globally without exception, specifically, locals last as long as the {} they are defined in, in all cases. Minor point, but a point.

Finally, in order to do what you want, you can add a set of brackets around the try catch. Gives you the scope you want, although it does come at the cost of a little readability, but not too much.

{
     String s;
     try
     {
          s = "test";
          //More code
     }
     catch
     {
          Console.WriteLine(s);
     }
}
拔了角的鹿 2024-07-12 18:36:01

当你有一个 try catch 时,你最多应该知道它可能抛出的错误。 这些异常类通常会告诉您有关异常的所有信息。 如果没有,您应该创建自己的异常类并传递该信息。 这样,您将永远不需要从 try 块内部获取变量,因为异常是自我解释的。 因此,如果您需要大量执行此操作,请考虑您的设计,并尝试考虑是否有其他方法,您可以预测即将到来的异常,或者使用来自异常的信息,然后可能重新抛出您自己的异常例外并提供更多信息。

When you have a try catch, you should at the most part know that errors that it might throw. Theese Exception classes normaly tell everything you need about the exception. If not, you should make you're own exception classes and pass that information along. That way, you will never need to get the variables from inside the try block, because the Exception is self explainatory. So if you need to do this alot, think about you're design, and try to think if there is some other way, that you can either predict exceptions comming, or use the information comming from the exceptions, and then maybe rethrow your own exception with more information.

往日 2024-07-12 18:36:01

正如其他用户所指出的,花括号定义了我所知道的几乎所有 C 风格语言的范围。

如果它是一个简单的变量,那么你为什么关心它在范围内的长度? 这没什么大不了的。

在C#中,如果它是一个复杂的变量,你将需要实现IDisposable。 然后,您可以使用 try/catch/finally 并在 finally 块中调用 obj.Dispose() 。 或者您可以使用 using 关键字,它将自动调用代码部分末尾的 Dispose。

As has been pointed out by other users, the curly braces define scope in pretty much every C style language that I know of.

If it's a simple variable, then why do you care how long it will be in scope? It's not that big a deal.

in C#, if it is a complex variable, you will want to implement IDisposable. You can then either use try/catch/finally and call obj.Dispose() in the finally block. Or you can use the using keyword, which will automatically call the Dispose at the end of the code section.

伴随着你 2024-07-12 18:36:01

在 Python 中,如果声明它们的行没有抛出异常,它们在 catch/finally 块中可见。

In Python they are visible in the catch/finally blocks if the line declaring them didn't throw.

紫轩蝶泪 2024-07-12 18:36:01

如果在变量声明之上的某些代码中抛出异常怎么办? 这意味着,在本例中声明本身并未发生。

try {

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

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

What if the exception is thrown in some code which is above the declaration of the variable. Which means, the declaration itself was not happend in this case.

try {

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

} catch (Exception) {
        //Use s;//Problem here
} finally {
        //Use s;//Problem here
}
别念他 2024-07-12 18:36:01

无论如何,在 C++ 中,自动变量的范围受到围绕它的花括号的限制。 为什么有人会期望通过在大括号之外插入 try 关键字来实现不同的结果呢?

In C++ at any rate, the scope of an automatic variable is limited by the curly braces that surround it. Why would anyone expect this to be different by plunking down a try keyword outside the curly braces?

鹿! 2024-07-12 18:36:01

您如何确定您已到达 catch 块中的声明部分? 如果实例化抛出异常怎么办?

How could you be sure, that you reached the declaration part in your catch block? What if the instantiation throws the exception?

橪书 2024-07-12 18:36:01

传统上,在 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 的范围是条件、then 子句和 else 子句。

Traditionally, in C-style languages, what happens inside the curly braces stays inside the curly braces. I think that having the lifetime of a variable stretch across scopes like that would be unintuitive to most programmers. You can achieve what you want by enclosing the try/catch/finally blocks inside another level of braces. e.g.

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

EDIT: I guess every rule does have an exception. The following is valid C++:

int f() { return 0; }

void main() 
{
    int y = 0;

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

The scope of x is the conditional, the then clause and the else clause.

听不够的曲调 2024-07-12 18:36:01

其他人都提出了基础知识——一个区块中发生的事情仍保留在一个区块中。 但对于 .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 规范中也是如此。

范围可以保护您,但您有时确实需要解决它。 随着时间的推移,你会习惯它,并且开始感觉很自然。 就像其他人所说的那样,一个块中发生的事情将保留在该块中。 你想分享一些东西吗? 你必须走出街区......

Everyone else has brought up the basics -- what happens in a block stays in a block. But in the case of .NET, it may be helpful to examine what the compiler thinks is happening. Take, for example, the following try/catch code (note that the StreamReader is declared, correctly, outside the blocks):

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();
        }
    }
}

This will compile out to something similar to the following 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

What do we see? MSIL respects the blocks -- they're intrinsically part of the underlying code generated when you compile your C#. The scope isn't just hard-set in the C# spec, it's in the CLR and CLS spec as well.

The scope protects you, but you do occasionally have to work around it. Over time, you get used to it, and it begins to feel natural. Like everyone else said, what happens in a block stays in that block. You want to share something? You have to go outside the blocks ...

|煩躁 2024-07-12 18:36:01

两件事:

  1. 一般来说,Java 只有 2 个级别的作用域:全局作用域和函数作用域。 但是,try/catch 是一个例外(没有双关语)。 当引发异常并且异常对象获得分配给它的变量时,该对象变量仅在“catch”部分中可用,并在 catch 完成后立即销毁。

  2. (更重要的是)。 您无法知道 try 块中的何处引发了异常。 它可能是在声明变量之前。 因此不可能说出哪些变量可用于 catch/finally 子句。 考虑以下情况,其中范围界定如您所建议:

    <前><代码>
    尝试
    {
    throw new ArgumentException("某些操作抛出异常");
    字符串 s =“废话”;
    }
    catch(e 作为 ArgumentException)
    {
    Console.Out.WriteLine(s);
    }

这显然是一个问题 - 当您到达异常处理程序时, s 将尚未被声明。 鉴于 catch 旨在处理异常情况,并且最终必须执行,因此在编译时安全并声明这是一个问题比在运行时要好得多。

Two things:

  1. Generally, Java has just 2 levels of scope: global and function. But, try/catch is an exception (no pun intended). When an exception is thrown and the exception object gets a variable assigned to it, that object variable is only available within the "catch" section and is destroyed as soon as the catch completes.

  2. (and more importantly). You can't know where in the try block the exception was thrown. It may have been before your variable was declared. Therefore it is impossible to say what variables will be available for the catch/finally clause. Consider the following case, where scoping is as you suggested:

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

This clearly is a problem - when you reach the exception handler, s will not have been declared. Given that catches are meant to handle exceptional circumstances and finallys must execute, being safe and declaring this a problem at compile time is far better than at runtime.

时光病人 2024-07-12 18:36:01

如果我们暂时忽略范围块问题,那么在没有明确定义的情况下,编译器将不得不更加努力地工作。 虽然这并非不可能,但作用域错误也迫使您(代码的作者)意识到您编写的代码的含义(catch 块中的字符串 s 可能为 null)。 如果您的代码是合法的,则在发生 OutOfMemory 异常的情况下,甚至不能保证为 s 分配内存槽:

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

CLR(以及编译器)还会强制您在使用变量之前对其进行初始化。 在所提供的 catch 块中它不能保证这一点。

因此,最终编译器必须做很多工作,这在实践中并没有提供太多好处,而且可能会让人们感到困惑,并导致他们问为什么 try/catch 的工作方式不同。

除了一致性之外,通过不允许任何花哨的东西并遵守整个语言中使用的已建立的作用域语义,编译器和 CLR 能够为 catch 块内的变量状态提供更好的保证。 它存在并且已被初始化。

请注意,语言设计者在其他结构(如 usinglock)方面做得很好,其中问题和范围得到了很好的定义,这使您可以编写更清晰的代码。

例如,带有 IDisposable 对象的 using 关键字

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

相当于:

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

如果你的 try/catch/finally 很难理解,请尝试重构或引入另一层间接层封装您要完成的任务的语义的类。 如果没有看到真正的代码,就很难更具体。

If we ignore the scoping-block issue for a moment, the complier would have to work a lot harder in a situation that's not well defined. While this is not impossible, the scoping error also forces you, the author of the code, to realise the implication of the code you write (that the string s may be null in the catch block). If your code was legal, in the case of an OutOfMemory exception, s isn't even guaranteed to be allocated a memory slot:

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

The CLR (and therefore compiler) also force you to initialize variables before they are used. In the catch block presented it can't guarantee this.

So we end up with the compiler having to do a lot of work, which in practice doesn't provide much benefit and would probably confuse people and lead them to ask why try/catch works differently.

In addition to consistency, by not allowing anything fancy and adhering to the already established scoping semantics used throughout the language, the compiler and CLR are able to provide a greater guarantee of the state of a variable inside a catch block. That it exists and has been initialized.

Note that the language designers have done a good job with other constructs like using and lock where the problem and scope is well defined, which allows you to write clearer code.

e.g. the using keyword with IDisposable objects in:

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

is equivalent to:

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

If your try/catch/finally is hard to understand, try refactoring or introducing another layer of indirection with an intermediate class that encapsulates the semantics of what you are trying to accomplish. Without seeing real code, it's hard to be more specific.

做个少女永远怀春 2024-07-12 18:36:01

它们不在同一范围内的部分原因是因为在 try 块的任何点上,您都可能抛出异常。 如果它们在同一范围内,那么等待就是一场灾难,因为根据抛出异常的位置,它可能会更加模糊。

至少当它在 try 块之外声明时,您可以确定抛出异常时该变量至少可能是什么; try 块之前的变量值。

Part of the reason they are not in the same scope is because at any point of the try block, you can have thrown the exception. If they were in the same scope, its a disaster in waiting, because depending on where the exception was thrown, it could be even more ambiguous.

At least when its declared outside of the try block, you know for sure what the variable at minimum could be when an exception is thrown; The value of the variable before the try block.

离笑几人歌 2024-07-12 18:36:01

C# 规范 (15.2) 规定“在块中声明的局部变量或常量就是该块。”

(在第一个示例中,try 块是声明“s”的块)

The C# Spec (15.2) states "The scope of a local variable or constant declared in a block ist the block."

(in your first example the try block is the block where "s" is declared)

长发绾君心 2024-07-12 18:36:01

我的想法是,因为 try 块中的某些内容触发了异常,所以它的名称空间内容不可信 - 即引用 catch 块中的 String 's' 可能会导致抛出另一个异常。

My thought would be that because something in the try block triggered the exception its namespace contents cannot be trusted - ie referencing the String 's' in the catch block could cause the throw of yet another exception.

最丧也最甜 2024-07-12 18:36:01

好吧,如果它不抛出编译错误,并且您可以为该方法的其余部分声明它,那么就无法仅在 try 范围内声明它。 它迫使您明确变量应该存在的位置并且不做出假设。

Well if it doesn't throw a compile error, and you could declare it for the rest of the method, then there would be no way to only declare it only within try scope. It's forcing you to be explicit as to where the variable is supposed to exists and doesn't make assumptions.

雨后彩虹 2024-07-12 18:36:01

如果赋值操作失败,您的 catch 语句将返回一个指向未赋值变量的空引用。

If the assignment operation fails your catch statement will have a null reference back to the unassigned variable.

望喜 2024-07-12 18:36:01

可以声明公共属性,而不是局部变量; 这也应该避免未分配变量的另一个潜在错误。
公共字符串 S { 得到; 放; }

Instead of a local variable, a public property could be declared; this also should avoid another potential error of an unassigned variable.
public string S { get; set; }

離人涙 2024-07-12 18:36:01

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;
})();

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;
})();
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文