异常处理(矛盾的文档/try-finally vs. using)

Bin*_*rus 6 c# using exception try-catch

我以为我已经了解 C# 中的异常处理是如何工作的。重新阅读文档以获得乐趣和自信,我遇到了问题:

该文档声称以下两个代码片段是等效的,甚至在编译时将第一个代码片段转换为后一个代码片段。

using (Font font1 = new Font("Arial", 10.0f)) {
  byte charset = font1.GdiCharSet;
}
Run Code Online (Sandbox Code Playgroud)

{
  Font font1 = new Font("Arial", 10.0f);
  try {
    byte charset = font1.GdiCharSet;
  }
  finally {
    if (font1 != null)
      ((IDisposable)font1).Dispose();
  }
}
Run Code Online (Sandbox Code Playgroud)

此外,它还声称:

using 语句确保 Dispose 被调用,即使在您调用对象的方法时发生异常。

相比之下,该文件指出:

在处理的异常中,finally保证运行关联的块。但是,如果未处理异常,则finally块的执行 取决于触发异常展开操作的方式。

我不明白这一点。在第一个文档的代码示例中,异常显然是未处理的(因为没有 catch 块)。现在,如果第二个文档中的语句为真,finally不能保证执行该块。这最终与第一个文件所说的(“using声明确保......”)(强调我的)相矛盾。

那么真相是什么呢?

编辑 1

我还是不明白。StevieB 的回答让我从 C# 语言规范中阅读了更多部分。在 16.3 节中,我们有:

[...] 这种搜索一直持续到找到可以处理当前异常的 catch 子句为止。 [...] 一旦找到匹配的 catch 子句,系统准备将控制转移到 catch 子句的第一个语句。在开始执行 catch 子句之前,系统首先按顺序执行任何与 try 语句相关联的 finally 子句比捕获异常的语句嵌套得更多。

所以我做了一个简单的测试程序,其中包含产生被零除的代码并且在一个try块内。该异常从未在我的任何代码中捕获,但相应的try语句有一个finally块:

int b = 0;

try {
  int a = 10 / b;
}
finally {
  MessageBox.Show("Hello");
}
Run Code Online (Sandbox Code Playgroud)

最初,根据上面的文档片段,我原以为该finally块永远不会被执行,并且程序在没有附加调试器的情况下执行时就会死亡。但这种情况并非如此; 而是显示了我们都非常熟悉的“异常对话框”,然后出现了“您好”对话框。

想了一会儿有关,并具有像阅读文档,文章和问题后,经过这个那个,很显然,这个“例外对话框中的”由)内置于Application.Run(一个标准的异常处理程序和其他通常的生产可以“启动”您的程序的方法,所以我不再想知道为什么finally要运行该块。

但是我仍然完全感到困惑,因为“您好”对话框出现“异常对话框”之后。上面的文档片段非常清楚(好吧,可能我又太傻了):

CLR 将找不到catchtry发生被零除的语句关联的子句。所以它应该将异常向上传递给调用者,catch在那里也找不到匹配的子句(那里甚至没有try语句)等等(如上所述,我不处理(即捕获)任何异常在这个测试程序中)。

最后,异常应该满足 CLR 的默认捕获所有异常处理程序(即默认情况下在 Application.Run() 及其朋友中处于活动状态的异常处理程序),但是(根据上面的文档)CLR 现在应该执行所有finally执行 CLR 捕获所有默认处理程序的块之前finally,比默认处理程序嵌套更深(“我的”块属于这些,不是吗?)。catch

这意味着“Hello”对话框应该出现“异常对话框”之前,不是吗?嗯,显然,情况正好相反。有人可以详细说明一下吗?

Jon*_*nna 6

该文档声称以下两个代码片段是等效的

他们是。

using 语句确保 Dispose 被调用,即使在您调用对象的方法时发生异常。

差不多。

这最终与第一个文件所说的相矛盾

嗯,第一个有点太模糊了,而不是完全不正确。

有些情况下 afinally不会运行,包括 a 隐含的情况using。AStackOverflowException将是一个例子(一个真正的堆栈溢出,如果你只是这样做throw new StackOverflowException()最终会运行)。

所有示例都是您无法捕捉到的东西,并且您的应用程序正在停机,因此如果清除using仅在应用程序运行时很重要,那么没问题finally

如果即使在程序崩溃时清理也很重要,那么finally永远都不够,因为它无法处理例如拔掉电源插头的情况,在这种情况下,即使在崩溃时清理也很重要,是一个需要考虑的案例。

在任何情况下,异常被进一步捕获并且程序继续finally运行,将运行。

对于未捕获的可捕获异常,finally块通常会运行,但仍然存在一些异常。一种情况是,如果try-finally在终结器中并且try花费了很长时间;在终结器队列上一段时间后,应用程序将快速失败。


小智 -3

首先,我想提醒您注意,using语句不能用于所有类型。这只能用于实现IDisposable接口的类型,该接口具有自动处置对象的功能。这存在于您提到的第二个文档中

C# 还包含 using 语句,它以方便的语法为 IDisposable 对象提供类似的功能。

这意味着如果发生未处理的异常,则由该类型的 Dispose() 方法处理对象的清理(这是针对 using 语句文档给出的)

进入查询,即使您的finally块(生成的)不保证针对未处理的异常运行,对象处置操作在运行时由.Net CLR处理

希望这能消除您的疑虑

  • 我看不出这如何回答这个问题 (5认同)