【发布时间】: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。
【问题讨论】:
只是好奇:为什么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
现在第二次捕获属于第一次还是第二次尝试?最后呢?因此,您会看到可选/多个部分满足要求。
【讨论】:
EEE 在 b 为 false 时执行,这同样适用于 if (b) if (c) DDD(); else EEE();。
更新:这个问题是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 并假装这就是您最初所说的。
【讨论】:
try{try/catch}finally 而不仅仅是try/catch/finally。
catch 和finally 的用途非常不同,您多久会看到它们在一个语句中结合使用?
catch 和 finally 的组合几乎没有(即远低于 1%)用于同一语句(或在其他语言中)嵌套语句),因为它们有两个非常不同的目的。这就是为什么我不想混合它们,如果我这样做,使用嵌套结构来强调目的和执行顺序的差异。每天学习新事物:您的occasionally 百分比明智吗?
或多或少,这是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 这样的语言没有这个问题,因为缩进和空格很重要。
【讨论】:
if(isTrue) { curlies++; }.
else 属于哪个if 完全一样。悬空的else 始终属于最新的if,这就是它的缩进方式。如果try-catch 有正确的语法可以删除大括号,我想自动缩进将解决程序员可能遇到的任何歧义。对我来说,带有不必要大括号的代码变得不那么可读,因为它只是分散了太多空间。我确实更喜欢紧凑性。
如果您假设 C# 的设计者只是选择使用与 C++ 相同的语法,那么问题就变成了为什么单个语句需要大括号 try 和 catch C++ 中的块。简单的答案是Bjarne Stroustrup 认为语法更容易解释。
在The Design and Evolution of C++ Stroustrup 写道:
“try 关键字是完全多余的,{ } 大括号也是如此,除非在 try 块或处理程序中实际使用了多个语句。”
他接着举了一个例子,其中不需要 try 关键字和 { }。然后他写道:
“但是,我发现这很难解释,因此引入冗余是为了让支持人员免于困惑的用户。”
参考: Stroustrup, Bjarne (1994)。 C++的设计和演变。艾迪生-韦斯利。
【讨论】:
我能想到的第一个想法是花括号创建了一个具有自己变量范围的块。
看下面的代码
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 已经初始化。
【讨论】:
foo 也会导致编译器错误(因为您没有在 catch 内分配 foo )。因此,如果您可以在没有花括号的情况下执行 try/catch,则您必须遵循与声明语句相关的其他块语句相同的规则。
Embedded statement cannot be a declaration or labeled statement。这与在无括号 using 语句之后声明变量时收到的错误消息相同。而using 语句肯定会创建一个新范围。也许有一个我不知道的细微差别?
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
}
【讨论】:
可能会阻止过度使用。 try-catch 块又大又丑,你会在使用它时注意到它。这反映了捕获对应用程序性能的影响 - 与简单的布尔测试相比,捕获异常非常慢。
一般来说,您应该避免错误,而不是处理错误。在您给出的示例中,更有效的方法是使用
if(!int.TryParse(s, out i))
i=0;
【讨论】:
理由是它更易于维护(更容易更改,更不容易损坏,因此质量更高):
至于为什么异常处理不同于条件表达式...
异常处理将向上遍历堆栈/范围,直到找到一个可以捕获所抛出异常类型的 Catch 块。强制范围标识符简化了对块的检查。在处理异常时强迫您确定范围似乎是个好主意,这也很好地表明这是异常处理的一部分,而不是普通代码。异常就是异常,不是您真正希望正常发生但知道可能发生并希望在它们确实发生时处理的事情。
编辑:我能想到的还有一个原因,就是与 ELSE 不同,在 TRY 之后 CATCH 是强制性的。因此需要有明确的方法来定义 TRY 块。
【讨论】:
lock 和 using 语句不需要 {} 块语法,它们不是“有条件的”。
do i = 1+ 1; while (true);。 do 块的末尾在哪里?
另一种看待这个的方式......
考虑到由“if”、“while”、“for”和“foreach”语句造成的所有维护问题,许多公司的编码标准总是要求基于“块”。
所以他们让你写作:
if (itIsSo)
{
ASingleLineOfCode();
}
然后:
if (itIsSo)
ASingleLineOfCode();
(注意缩进不是编译器检查的,不能依赖它是正确的)
设计一种总是需要基础的语言是一个很好的案例,但是由于必须始终使用基础,太多的人会讨厌 C#。然而,对于 try/catch 来说,不使用底座就无法逃脱的期望,因此可以要求它们而不会引起很多人的抱怨。
如果有一个选择,我更愿意使用 if/endIf(和 while/endWhile)作为块分隔符,但美国在那个分隔符上取得了成功。 (C 必须定义大多数语言的样子而不是 Module2,毕竟我们所做的大部分事情都是由历史而非逻辑定义的)
【讨论】:
最简单(我认为)的答案是 C/C++/C# 中的每个代码块都需要大括号。
编辑#1
针对反对票,直接来自 MSDN:
try-catch 语句由一个 try block 和一个或多个 catch 子句组成,这些子句为不同的异常指定处理程序。
根据定义,它是一个块,所以它需要花括号。这就是为什么我们不能在没有{ } 的情况下使用它。
【讨论】:
while、if 或 for 之后,您可以有一个语句(没有大括号),所以 @Shlomo 询问为什么 C# 和 Java 中的 try 不是这种情况。
try 块中有多个指令。您可能有try...catch、try...finally 或try...catch...finally。 try 指令本身就是一个块。如果您与if、while、for 进行比较,根据try 块的反对,没有其他指令与这些组合。所以,try 绝对是一个块,块需要花括号。最后总结出和我的回答一样的结论,只是我的回答比这个评论简单。