只是好奇:为什么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
。
考虑一下这里存在三个(或更多)代码块的事实:
try {}
catch (myexcption)
{}
catch (myotherexception)
{}
finally
{}
请记住,这些都在更大的上下文范围内,未捕获的异常会在堆栈中进一步发挥作用。
请注意,这与具有{}结构的类构造基本相同。
比如说你可能有:
try
try
if (iAmnotsane)
beatMe(please);
catch (Exception myexception)
catch (myotherexception)
logerror("howdy")
finally
现在第二次尝试属于第一次或第二次尝试吗?最后怎么样?所以你看到可选/多个部分符合要求。
最简单的(我认为)答案是C / C ++ / C#中的每个代码块都需要花括号。
编辑#1
针对负面投票,直接来自MSDN:
try-catch语句包含一个try块,后跟一个或多个catch子句,这些子句为不同的异常指定处理程序。
根据定义,它是一个块,所以它需要花括号。这就是为什么我们不能在没有{ }
的情况下使用它。
更新:这个问题是my blog on December 4th, 2012的主题。博客上有许多你可能也感兴趣的富有洞察力的评论。感谢您提出的好问题!
正如其他人所指出的那样,所提出的特征引入了令人困惑的模糊性。我有兴趣看看是否有任何其他理由决定不支持该功能,所以我检查了语言设计说明存档。
我在语言设计说明档案中看不出任何可以证明这一决定的理由。据我所知,C#就是这样做的,因为这是其他具有类似语法的语言的做法,并且由于模糊性问题,他们就是这样做的。
我确实学到了一些有趣的东西。在C#的初始设计中,没有try-catch-finally!如果你想尝试使用catch和a finally,那么你必须写:
try
{
try
{
XYZ();
}
catch(whatever)
{
DEF();
}
}
finally
{
ABC();
}
毫不奇怪,这正是编译器分析try-catch-finally的原因;它只是在最初的分析中将它分解为try-catch内部的try-catch,并假装这首先是你所说的。
或多或少,这是在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块始终与最接近的try相关联,但我发现此解决方案通常允许程序员编写具有潜在危险的代码。例如,在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这样的语言没有这个问题,因为缩进和空白很重要。
如果你假设C#的设计者只是选择使用与C ++相同的语法,那么问题就变成了为什么单个语句需要使用大括号来尝试和捕获C ++中的块。简单的答案是Bjarne Stroustrup认为语法更容易解释。
在The Design and Evolution of C++ Stroustrup写道:
“try关键字是完全冗余的,{}括号也是如此,除非在try-block或handler中实际使用了多个语句。”
他继续举例说明不需要try关键字和{}。然后他写道:
“但是,我发现这很难解释为了节省支持人员从困惑的用户那里引入冗余。”
参考文献:Stroustrup,Bjarne(1994)。 C ++的设计与演变。 Addison-Wesley出版社。
我能想到的第一个想法是花括号创建一个具有自己的变量范围的块。
请查看以下代码
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被初始化。
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块很大而且很难看,当你使用它时你会注意到它。这反映了catch对应用程序性能的影响 - 与简单的布尔测试相比,捕获异常非常慢。
一般来说,你应该避免错误,而不是处理它们。在您给出的示例中,将使用更有效的方法
if(!int.TryParse(s, out i))
i=0;
理性的是它更易于维护(更容易更改,更不容易破坏,更高质量):
至于为什么异常处理与条件表达式不同......
异常处理将遍历堆栈/作用域,直到找到一个Catch块,它将捕获抛出的异常类型。强制范围标识符可以简化对块的检查。在处理异常时强制您使用范围似乎是个好主意,它也是一个很好的迹象,表明这是异常处理的一部分而不是普通代码。例外是例外,不是你真正想要正常发生的事情,但知道可能发生并且想要在它们发生时进行处理。
编辑:还有一个我能想到的原因,就是在不同于ELSE的TRY之后,CATCH是强制性的。因此,需要有明确的方法来定义TRY块。
另一种看待这个的方式......
鉴于由“if”,“while”,“for”和“foreach”语句创建的所有维护问题都没有基础,许多公司的编码标准始终需要基于作用于“块”的语句。
所以他们让你写:
if (itIsSo)
{
ASingleLineOfCode();
}
而不是:
if (itIsSo)
ASingleLineOfCode();
(注意,由于编译器没有检查缩进,因此不能依赖于缩进)
设计一种总是需要基础的语言是一个很好的例子,但是由于不得不总是使用基础,太多人会讨厌C#。然而,对于尝试/捕获没有期望能够在不使用基础的情况下逃脱,因此有可能在没有许多人抱怨的情况下要求它们。
给定一个选择我宁愿将if / endIf(和/ endWhile)作为块分隔符,但是美国在那个方面得到了解决方案。 (C必须定义大多数语言的样子而不是Module2,毕竟我们所做的大部分是由历史而不是逻辑定义的)