为什么Try-Catch需要大括号
只是好奇:为什么在C# (Java也?) 尝试catch的语法硬编码为多个语句? 为什么语言不允许:
int i; string s = DateTime.Now.Seconds % 2 == 1 ? "1" : "not 1"; try i = int.Parse(s); catch i = 0;
这个例子只是为了平凡的目的。 我知道有int.TryParse
。
考虑一下这里有三个(或更多)代码块的事实:
try {} catch (myexcption) {} catch (myotherexception) {} finally {}
请记住,这是在一个更大的范围内的范围,没有捕获的例外是有力地抓住进一步堆栈。
请注意,这与具有{}结构的类构造基本相同。
举例来说,你可能有:
try try if (iAmnotsane) beatMe(please); catch (Exception myexception) catch (myotherexception) logerror("howdy") finally
现在呢,第二次抓到属于第一次还是第二次? 那最后呢? 所以你看到可选/多个部分的要求。
更新:这个问题是我的博客在2012年12月4日的主题 。 在博客上有许多有意思的评论,你可能也会感兴趣。谢谢你的提问!
正如其他人所指出的那样,所提出的特征引入了混淆的含糊之处。 我有兴趣看看是否有任何其他的理由不支持该function,所以我检查了语言devise笔记归档。
我在语言devise笔记档案中没有看到任何证据certificate这个决定是合理的。 据我所知,C#是这样做的,因为这就是其他类似语法的语言如何做的,而且他们这样做是因为含糊不清的问题。
我确实学到了一些有趣的东西。 在C#的最初devise中,没有try-catch-finally! 如果你想要一个catch和一个尝试,那么最后你必须写:
try { try { XYZ(); } catch(whatever) { DEF(); } } finally { ABC(); }
这并不奇怪,编译器是如何分析try-catch-finally的; 它只是在最初的分析中把它分解成try-catch,然后假装你说的就是这个。
或多或less,这是一个悬而未决的问题 。
例如,
if( blah ) if ( more blah ) // do some blah else // no blah I suppose
如果没有大括号,其他的含糊不清,因为你不知道它是否与第一或第二条语句相关联。 所以,如果你不想让这样的模糊不清,那么你必须回退一个编译器约定(例如,在Pascal或C中,编译器假定其他的悬挂与最接近的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块总是和最接近的try相关联,但是我发现这个解决scheme通常允许程序员编写有潜在危险的代码。 例如,在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语句相关联。 所以我认为要求使用大括号可以大大减less歧义,避免一个相当狡猾的bug(即使在代码检查过程中,这些问题也是微不足道的)。 像Python这样的语言没有这个问题,因为缩进和空白的问题。
如果您认为C#的devise者只是select使用与C ++相同的语法,那么问题就变成了为什么使用单个语句的花括号尝试和捕获C ++中的块的原因。 简单的答案是Bjarne Stroustrup认为语法更容易解释。
在C ++ Stroustrup 的devise和发展中写道:
“try关键字是完全冗余的,除了在try-block或handler中实际使用多个语句的情况下,{}括号也是如此。”
他接着给出了一个不需要try关键字和{}的例子。 他然后写道:
“但是,我觉得这很难解释,引入冗余是为了从困惑的用户中拯救支持人员。”
参考:Stroustrup,Bjarne(1994)。 C ++的devise与发展。 Addison-Wesley出版社。
首先想到的是,大括号使用自己的variables范围来创build块。
看下面的代码
try { int foo = 2; } catch (Exception) { Console.WriteLine(foo); // The name 'foo' does not exist in the current context }
由于variables作用域, foo
在catch块中不可访问。 我认为这样可以更容易地推断variables在使用前是否被初始化了。
与此代码进行比较
int foo; try { foo = 2; } catch (Exception) { Console.WriteLine(foo); // Use of unassigned local variable 'foo' }
在这里你不能保证foo被初始化。
try // 1 try // 2 something(); catch { // A } catch { // B } catch { // C }
B捕获尝试1或2?
我不认为你可以毫不含糊地解决这个问题,因为片段可能意味着:
try // 1 { try // 2 something(); catch { // A } } catch { // B } catch { // C } try // 1 { try // 2 something(); catch { // A } catch { // B } } catch { // C }
可能会阻止过度使用。 try-catch块很大很丑,你会注意到什么时候使用它。 这反映了捕获对应用程序性能的影响 – 捕获exception与简单的布尔testing相比非常慢。
一般来说,你应该避免错误,而不是处理它们。 在你给的例子中,更有效的方法是使用
if(!int.TryParse(s, out i)) i=0;
理性的是,它更易于维护(更易于改变,不太可能破坏,更高质量):
- 它更清晰,
- 它更容易改变,因为如果你需要添加一行到你的块,你不会引入一个错误。
至于为什么exception处理不同于条件expression式…
- 如果/ Else以expression式为条件,则在代码中使用两个(或更多的If / Else if / Else)path
- Try / Catch是exception处理的一部分,它不是一个条件expression式。 Try / Catch / Finally只有在Try块的范围内抛出exception时才会运行。
exception处理将遍历堆栈/范围,直到find一个将捕获抛出的exceptiontypes的Catch块。 强制范围标识符可以简化块的检查。 强制你在处理exception时的范围似乎是一个好主意,这也是一个很好的指示,这是exception处理的一部分,而不是普通的代码。 例外情况是例外情况,而不是你真正想要正常发生的事情,但知道可以发生,并希望在发生时处理。
编辑:还有一个我能想到的原因是CATCH是TRY之后强制的,不像ELSE。 因此需要明确的方法来定义TRY块。
看这个的另一种方法
考虑到由“if”,“while”,“for”和“foreach”语句造成的所有维护问题,许多公司都有编码标准,这些标准总是要求基于“块”的语句。
所以他们让你写:
if (itIsSo) { ASingleLineOfCode(); }
而不是:
if (itIsSo) ASingleLineOfCode();
(注意,由于缩进不是由编译器检查的,不能依赖于正确的)
一个好的例子可以用来devise一个总是需要基础的语言,但是那么太多的人会因为不得不总是使用这个基础而憎恨C#。 然而,对于try / catch而言,如果不使用这些基地,就不会期望能够逃脱,所以有可能要求他们没有很多人抱怨。
给定一个select,我宁愿有if / endIf(和while / endWhile)作为块分隔符,但美国在这方面取得了进展。 (C需要定义大多数语言而不是Module2,毕竟我们所做的大部分工作都是由历史而不是逻辑定义的)
最简单的(我认为)答案是C / C ++ / C#中的每个代码块都需要大括号。
编辑#1
为了回应MSDN的直接反对票:
try-catch(C#参考)
try-catch语句包含一个try 块,后跟一个或多个catch子句,它们为不同的exception指定处理程序。
按照定义说,这是一个块,所以它需要大括号。 这就是为什么我们不能在没有{ }
情况下使用它。