【问题标题】:Why does Try-Catch require curly braces为什么 Try-Catch 需要花括号
【发布时间】:2010-10-22 17:35:54
【问题描述】:

只是好奇:为什么try catch in C#(Java 也是?)的语法对多个语句进行硬编码?为什么语言不允许:

int i;
string s = DateTime.Now.Seconds % 2 == 1 ? "1" : "not 1";
try
   i = int.Parse(s);
catch
   i = 0;

该示例仅用于微不足道的目的。我知道有int.TryParse

【问题讨论】:

标签: c# try-catch


【解决方案1】:

考虑一下这里实际上有三个(或更多)代码块在起作用的事实:

try {}
catch (myexcption)
{}
catch (myotherexception)
{}
finally
{}

请记住,这些都在更大的上下文范围内,未捕获的异常可能会被进一步捕获到堆栈中。

请注意,这与同样具有 {} 结构的类构造基本相同。

比如说你可能有:

try
try
if (iAmnotsane)
beatMe(please);
catch (Exception myexception)
catch (myotherexception)
logerror("howdy")
finally

现在第二次捕获属于第一次还是第二次尝试?最后呢?因此,您会看到可选/多个部分满足要求。

【讨论】:

  • +1 用于提供不可读的代码 :) 话虽如此,嵌套的 IF-ELSE 块也有同样的问题。
  • IF-ELSE不同,因为IF-ELSE中最多有一个ELSE,而TRY-CATCH中有0个或多个CATCH。
  • 布赖恩是正确的。不可读的代码很棒。如果你有 if-if-statement-else (没有花括号),if-else 确实有同样的问题,那么 else 是不明确的它属于哪一个。按照惯例,我认为它属于第二个 if。
  • @user93422:我不同意。如果不需要大括号,则解析可能会被设计成所有连续的捕获都属于同一范围。当这不是意图时,这可能会强制使用大括号,但如果您希望 EEE 在 b 为 false 时执行,这同样适用于 if (b) if (c) DDD(); else EEE();
  • @Shlomo:完全有可能设计一个明确处理这种情况的语法。
【解决方案2】:

更新:这个问题是my blog on December 4th, 2012 的主题。您可能还会对博客上的许多有见地的 cmets 感兴趣。感谢您提出的好问题!


正如其他人所指出的,提议的功能引入了令人困惑的歧义。我很想知道是否还有其他理由可以决定不支持该功能,因此我查看了语言设计说明存档。

我在语言设计说明档案中没有看到任何证明这一决定的理由。据我所知,C# 这样做是因为其他具有相似语法的语言就是这样做的,而他们这样做是因为存在歧义问题。

我确实学到了一些有趣的东西。在 C# 的初始设计中,没有 try-catch-finally!如果您想尝试使用 catch 和 finally ,那么您必须编写:

try
{
  try
  {
      XYZ();
  }
  catch(whatever)
  {
     DEF();
  }
}
finally
{
  ABC();
}

毫不奇怪,这正是编译器分析 try-catch-finally 的方式;它只是在初步分析后将其分解为 try-catch 内的 try-finally 并假装这就是您最初所说的。

【讨论】:

  • Delphi(是的,它仍然存在)正好有这个问题:你必须做try{try/catch}finally 而不仅仅是try/catch/finally
  • 只是想知道:由于catchfinally 的用途非常不同,您多久会看到它们在一个语句中结合使用?
  • @JeroenWiertPluimers:大多数时候我使用其中一个。但偶尔会同时使用两者。
  • 我在我见过的大多数代码中的观察是,catchfinally 的组合几乎没有(即远低于 1%)用于同一语句(或在其他语言中)嵌套语句),因为它们有两个非常不同的目的。这就是为什么我不想混合它们,如果我这样做,使用嵌套结构来强调目的和执行顺序的差异。每天学习新事物:您的occasionally 百分比明智吗?
【解决方案3】:

或多或少,这是dangling else problem上的一出戏。

例如,

if( blah )
    if ( more blah )
        // do some blah
else
    // no blah I suppose

没有大括号,else 是不明确的,因为您不知道它是否与第一个或第二个 if 语句相关联。因此,您必须回退到编译器约定(例如,在 Pascal 或 C 中,编译器假定悬空 else 与最接近的 if 语句相关联)来解决歧义,或者如果您不想允许这种歧义,则编译完全失败首先。

同样,

try
    try
        // some code that throws!
catch(some blah)
    // which try block are we catching???
catch(more blah )
    // not so sure...
finally
    // totally unclear what try this is associated with.

您可以通过约定解决它,其中 catch 块始终与最接近的尝试相关联,但我发现这种解决方案通常允许程序员编写具有潜在危险的代码。例如,在 C 中,这样:

if( blah )
    if( more blah )
        x = blah;
    else
        x = blahblah;

...是编译器如何解释这个 if/if/else 块。但是,搞砸缩进并写入也是完全合法的:

if( blah )
    if( more blah )
        x = blah;
else
    x = blahblah;

...现在看起来 else 与外部 if 语句相关联,而实际上由于 C 约定,它与内部 if 语句相关联。所以我认为要求大括号对解决歧义和防止相当隐蔽的错误大有帮助(即使在代码检查期间,这些问题也很容易错过)。像 python 这样的语言没有这个问题,因为缩进和空格很重要。

【讨论】:

  • +1 向我介绍了悬空的 else 问题。 4 年的 C/C++ 计算机科学,我从未听说过。我只是总是使用 K&R 风格的卷发,所以我从来不用考虑它。我什至用单线做...if(isTrue) { curlies++; }.
  • 有一个事实,VS 自动缩进的方式与编译器理解哪个else 属于哪个if 完全一样。悬空的else 始终属于最新的if,这就是它的缩进方式。如果try-catch 有正确的语法可以删除大括号,我想自动缩进将解决程序员可能遇到的任何歧义。对我来说,带有不必要大括号的代码变得不那么可读,因为它只是分散了太多空间。我确实更喜欢紧凑性。
【解决方案4】:

如果您假设 C# 的设计者只是选择使用与 C++ 相同的语法,那么问题就变成了为什么单个语句需要大括号 try 和 catch C++ 中的块。简单的答案是Bjarne Stroustrup 认为语法更容易解释。

The Design and Evolution of C++ Stroustrup 写道:

“try 关键字是完全多余的,{ } 大括号也是如此,除非在 try 块或处理程序中实际使用了多个语句。”

他接着举了一个例子,其中不需要 try 关键字和 { }。然后他写道:

“但是,我发现这很难解释,因此引入冗余是为了让支持人员免于困惑的用户。”

参考: Stroustrup, Bjarne (1994)。 C++的设计和演变。艾迪生-韦斯利。

【讨论】:

    【解决方案5】:

    我能想到的第一个想法是花括号创建了一个具有自己变量范围的块。

    看下面的代码

    try
    {
        int foo = 2;
    }
    catch (Exception)
    {
        Console.WriteLine(foo); // The name 'foo' does not exist in the current context
    }
    

    foo 在 catch 块中由于变量作用域而无法访问。我认为这可以更容易地推断变量在使用之前是否已经初始化。

    与此代码比较

    int foo;
    try
    {
        foo = 2;
    }
    catch (Exception)
    {
        Console.WriteLine(foo); // Use of unassigned local variable 'foo'
    }
    

    这里不能保证 foo 已经初始化。

    【讨论】:

    • 我认为这是一个糟糕的论点。例如:if 语句 没有 花括号也开始一个新的(单行)范围。
    • 在第二个示例中,在 try/catch 块之后使用 foo 也会导致编译器错误(因为您没有在 catch 内分配 foo )。因此,如果您可以在没有花括号的情况下执行 try/catch,则您必须遵循与声明语句相关的其他块语句相同的规则。
    • @Greg : 尝试在 if 语句中声明和初始化一个变量
    • 我知道它创建了自己的范围。我不明白为什么这是必要的,当然也不是为什么语言设计者要求它是必要的。
    • @Hans - 结果:Embedded statement cannot be a declaration or labeled statement。这与在无括号 using 语句之后声明变量时收到的错误消息相同。而using 语句肯定会创建一个新范围。也许有一个我不知道的细微差别?
    【解决方案6】:
    try // 1
    try // 2
      something();
    catch { // A
    }
    catch { // B
    }
    catch { // C
    }
    

    B 捕获尝试 1 还是 2?

    我认为你不能明确地解决这个问题,因为 sn-p 可能意味着:

    try // 1
    {
        try // 2
            something();
        catch { // A
        }
    }
    catch { // B
    }
    catch { // C
    }
    
    
    try // 1
    {
        try // 2
            something();
        catch { // A
        }
        catch { // B
        }
    }
    catch { // C
    }
    

    【讨论】:

      【解决方案7】:

      可能会阻止过度使用。 try-catch 块又大又丑,你会在使用它时注意到它。这反映了捕获对应用程序性能的影响 - 与简单的布尔测试相比,捕获异常非常慢。

      一般来说,您应该避免错误,而不是处理错误。在您给出的示例中,更有效的方法是使用

      if(!int.TryParse(s, out i))
       i=0;
      

      【讨论】:

      • 永远避免错误,是的。但是,无论您多么小心或进行多少检查(例如,在编写处理文件系统或网络等 IO 设备的代码时),都可能发生错误。
      • 我敢打赌这种语法不是发明的,因为安德斯说:“我想知道我们是否可以让这种语法变得丑陋到足以避免使用。”
      • @Michael-Petito 显然它们是必要的构造,我只是不认为它们需要作为通用解决方案应用。
      • @柯克-沃尔
      【解决方案8】:

      理由是它更易于维护(更容易更改,更不容易损坏,因此质量更高):

      1. 更清晰,而且
      2. 更改更容易,因为如果您需要在块中添加一行,您不会引入错误。

      至于为什么异常处理不同于条件表达式...

      • If/Else 取决于表达式在代码中使用两个(或多个 If/Else if/Else)路径之一
      • Try/Catch 是异常处理的一部分,它不是条件表达式。 Try/Catch/Finally 仅在 Try 块范围内引发异常时才运行。

      异常处理将向上遍历堆栈/范围,直到找到一个可以捕获所抛出异常类型的 Catch 块。强制范围标识符简化了对块的检查。在处理异常时强迫您确定范围似乎是个好主意,这也很好地表明这是异常处理的一部分,而不是普通代码。异常就是异常,不是您真正希望正常发生但知道可能发生并希望在它们确实发生时处理的事情。

      编辑:我能想到的还有一个原因,就是与 ELSE 不同,在 TRY 之后 CATCH 是强制性的。因此需要有明确的方法来定义 TRY 块。

      【讨论】:

      • 我不同意你的类比。 lockusing 语句不需要 {} 块语法,它们不是“有条件的”。
      • @Kirk - 也许你是对的,但这只是我对这个问题的想法。
      • "... 需要有一种明确的方式来定义 TRY 块":是的,但这并不意味着这种方式必须使用花括号。以下是有效的 C# 代码:do i = 1+ 1; while (true);do 块的末尾在哪里?
      【解决方案9】:

      另一种看待这个的方式......

      考虑到由“if”、“while”、“for”和“foreach”语句造成的所有维护问题,许多公司的编码标准总是要求基于“块”。

      所以他们让你写作:

      if (itIsSo)
      {
      ASingleLineOfCode();
      }
      

      然后:

      if (itIsSo)
      ASingleLineOfCode();
      

      (注意缩进不是编译器检查的,不能依赖它是正确的)

      设计一种总是需要基础的语言是一个很好的案例,但是由于必须始终使用基础,太多的人会讨厌 C#。然而,对于 try/catch 来说,不使用底座就无法逃脱的期望,因此可以要求它们而不会引起很多人的抱怨。


      如果有一个选择,我更愿意使用 if/endIf(和 while/endWhile)作为块分隔符,但美国在那个分隔符上取得了成功。 (C 必须定义大多数语言的样子而不是 Module2,毕竟我们所做的大部分事情都是由历史而非逻辑定义的)

      【讨论】:

        【解决方案10】:

        最简单(我认为)的答案是 C/C++/C# 中的每个代码块都需要大括号。

        编辑#1

        针对反对票,直接来自 MSDN:

        try-catch (C# Reference)

        try-catch 语句由一个 try block 和一个或多个 catch 子句组成,这些子句为不同的异常指定处理程序。

        根据定义,它是一个块,所以它需要花括号。这就是为什么我们不能在没有{ } 的情况下使用它。

        【讨论】:

        • 首先,没有 C/C++/C# 之类的东西。其次,问题是“为什么这是一个块”?在 whileiffor 之后,您可以有一个语句(没有大括号),所以 @Shlomo 询问为什么 C# 和 Java 中的 try 不是这种情况。
        • @Kate Gregory:可能没有 C/C++/C# 这样的东西,但有 C++ 和 C#。就像我们说的Windows 95/98/NT/XP等一样。也没有这样的东西!此外,我们知道我们谈论的是 Windows 家族的不同版本。这和我在这里的意思是一样的。
        • @Kate Gregory:这是一个块,因为您在 try 块中有多个指令。您可能有try...catchtry...finallytry...catch...finallytry 指令本身就是一个块。如果您与ifwhilefor 进行比较,根据try 块的反对,没有其他指令与这些组合。所以,try 绝对是一个块,块需要花括号。最后总结出和我的回答一样的结论,只是我的回答比这个评论简单。
        • 这个想法的问题是if语句后面的块通常被称为if块。当您谈论它时,它被称为块,仅仅是因为这是一个好名字。没有人将 if 块称为单语句或块:)
        • if(条件)语句;语句可以是单个命令,也可以是复合命令,包含在 {} 中。这在 C 风格语言中是相当标准的。那么问题来了,在try-catch-finally之后是否需要做一些巧妙的语法解析来要求复合命令?
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2016-10-01
        • 1970-01-01
        • 2021-10-15
        • 2014-08-09
        • 2014-11-07
        • 1970-01-01
        • 2020-05-06
        相关资源
        最近更新 更多