我以为我已经理解了C#中的异常处理是如何工作的。重新阅读文档以获得乐趣和自信,我遇到了问题:
This document声称以下两个代码片段在编译时是第一个转换为后一个代码片段,甚至更多。
using (Font font1 = new Font("Arial", 10.0f)) {
byte charset = font1.GdiCharSet;
}
和
{
Font font1 = new Font("Arial", 10.0f);
try {
byte charset = font1.GdiCharSet;
}
finally {
if (font1 != null)
((IDisposable)font1).Dispose();
}
}
此外,它声称:
using语句确保即使调用Dispose也会调用 在对象上调用方法时发生异常。
相比之下,that document表示:
在已处理的异常中,保证关联的
finally
块 要运行。但是,如果异常未处理,则执行finally
块取决于异常展开操作的方式 触发。
我不是一起来的。在第一个文档的代码示例中,异常显然未处理(因为没有catch块)。现在,如果第二个文档中的语句为真,则finally
块不保证执行。这最终与第一份文件所说的相矛盾(" using
陈述确保 ...")(强调我的)。
那么真相是什么?
编辑1
我仍然没有得到它。 StevieB的回答让我从C#语言规范中读到更多部分。在第16.3节中,我们有:
[...]此搜索将继续,直到找到可以执行的catch子句 处理当前异常[...]一旦匹配的catch子句是 发现,系统准备将控制转移到第一个语句 catch子句。在执行catch子句开始之前, 系统首先按顺序执行任何最终的子句 与try语句相关联的嵌套比那个嵌套的更多 抓住了例外。
所以我做了一个简单的测试程序,其中包含产生除零的代码,并且在try
块内。该异常从未在我的任何代码中被捕获,但相应的try
语句具有finally
块:
int b = 0;
try {
int a = 10 / b;
}
finally {
MessageBox.Show("Hello");
}
最初,根据上面的文档片段,我曾预料到finally
块永远不会执行,并且程序在没有附加调试器的情况下执行时会死掉。但这种情况并非如此;相反,"例外对话框"我们都知道得太清楚了,之后,"你好"出现对话框。
在考虑了一段时间之后,在阅读了文档,文章和问题之后,例如this和that,很明显这个"异常对话框"由一个标准的异常处理程序生成,该处理程序内置在Application.Run()和其他常用的方法中,这些方法可以启动"你的程序,所以我不再想知道为什么运行finally
块。
但我仍然感到困惑,因为"你好"在"异常对话框"之后出现对话框。上面的文档片段非常清楚(好吧,我可能只是太傻了):
CLR找不到与catch
语句关联的try
子句,其中除以零发生。所以它应该将异常一级传递给调用者,也不会在那里找到匹配的catch
子句(那里甚至没有try
语句)等等(如上所述)上面,我没有处理(即捕获)此测试程序中的任何异常。)
最后,该异常应符合CLR的默认catch-all异常处理程序(即默认情况下在Application.Run()及其朋友中处于活动状态的处理程序),但是(根据上面的文档) CLR现在应该执行比默认处理程序嵌套更深的所有finally
块("我的" finally
块属于这些块,不是吗?)在执行CLR catch-all默认处理程序的catch
阻止之前。
这意味着"你好"对话框应该出现之前的"例外对话框",不是吗?嗯,显然,反之亦然。有人可以详细说明吗?
答案 0 :(得分:2)
本文档声称以下两个代码段是等效的
他们是。
using语句确保即使在对象上调用方法时发生异常也会调用Dispose。
非常。
这最终与第一份文件所说的相矛盾
嗯,第一个有点太模糊,而不是不正确。
有些情况会导致finally
无法运行,包括using
隐含的情况。一个StackOverflowException
就是一个例子(一个真实的例子来自堆栈,如果你只是throw new StackOverflowException()
,那么finally将会运行。)
所有示例都是您无法捕获的内容,并且您的应用程序正在关闭,因此如果从using
清除只在应用程序运行时很重要,那么finally
就可以了。
如果即使程序崩溃,清理工作也很重要,那么finally
永远不够,因为它无法处理,例如电源插头被拉出,在某种情况下,即使在碰撞中清理也很重要,这是一个需要考虑的情况。
在进一步发现异常且程序继续的任何情况下,finally
都会运行。
对于未捕获的可捕获异常,通常会运行finally
块,但仍有一些例外。如果try
- finally
在终结者中并且try
需要很长时间,那就是一个;在终结者队列上一段时间后,应用程序将快速失败。
答案 1 :(得分:0)
如果您定义了"未处理的异常" (它必须由相同的 catch
块中的try
子句处理才是正确的,没有理由允许构造
try {
...
}
finally {
...
}
根据您的定义,finally
块永远不会运行。由于上述结构是有效的,我们必须得出结论,你对未处理的异常的定义"是不正确的。
这意味着"如果异常不由任何异常处理程序处理,则在调用堆栈中的任何位置"。
答案 2 :(得分:0)
我相信(如果我错了,请纠正我)答案在于定义:
using语句确保即使调用Dispose也会调用 当您在对象上调用方法时,会出现异常。
因此,如果在使用对象调用的任何方法中发生任何异常,则最终确保运行。另一方面,如果在使用块内部调用其他与该对象无关的方法导致异常,则最终无法保证运行
答案 3 :(得分:0)
C#语言规范规定,对于System.Exception或其任何派生异常,将执行finally块(在using语句或其他地方)。当然,如果你得到一个通常的try..catch逻辑无法处理的异常,例如AccessViolationException所有的赌注都是关闭的,这就是模糊性的来源。
规范随Visual Studio 2013及更高版本一起安装 - 2017年它位于C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\VC#\Specifications\1033
在
部分8.9.5抛出语句
我们看到以下内容:
抛出异常时,将传输控制权 可以在封闭的try语句中的第一个catch子句 处理异常。从这个过程发生的过程 将控制转移到a的异常被抛出 合适的异常处理程序称为异常传播。 异常的传播包括反复评估 以下步骤,直到匹配异常的catch子句为止 找到。在本说明书中,投掷点最初是位置 抛出异常。
- 在当前的函数成员中, 检查包含抛出点的每个try语句。对于每一个 语句S,从最里面的try语句开始,以。结尾 最外层的try语句,评估以下步骤:
- 如果 如果S有一个或多个,则S的try块包含抛出点 catch子句,catch子句按外观顺序检查 找到合适的异常处理程序。第一个捕获条款 指定异常类型或异常类型的基本类型 被认为是一场比赛。一般的catch子句(第8.10节)被认为是a 匹配任何异常类型。如果找到匹配的catch子句, 异常传播是通过将控制转移到 该捕获条款的块。
- 否则,如果尝试阻止或捕获 S的块包围投掷点,如果S有一个finally块, 控制转移到finally块。如果是finally块 抛出另一个异常,处理当前异常是 终止。否则,当控制到达终点时 最后阻止,继续处理当前异常。
- 如果 当前函数中没有找到异常处理程序 调用,函数调用终止,其中之一 发生以下情况
- 如果当前函数是非异步的,则执行步骤 对于具有投掷点的函数的调用者,重复上面的操作 对应于函数成员所在的语句 调用。
- 如果当前函数是异步和任务返回,则 异常记录在返回任务中,该异常被置于故障状态 或取消状态,如§10.14.1。
中所述- 如果是当前功能 是异步和void返回,同步上下文 按照§10.14.2。
中的描述通知当前线程- 如果 异常处理终止了所有函数成员调用 当前线程,表示该线程没有处理程序 异常,然后线程本身终止。这样的影响 终止是实现定义的。
答案 4 :(得分:0)
你必须给文档留一点余地。在大多数情况下,有一个隐含的"在合理的范围内"。例如,如果我关闭计算机,则不会调用最终块或使用/ Disposes。
除了关闭计算机外,还有一些情况可以让操作系统终止您的应用程序 - 有效地将其关闭。在这些情况下,Dispose
和finally
不会被调用。这些通常是非常严重的错误条件,如堆栈外或内存不足。有一些复杂的情况可能会发生异常情况少于例外情况。例如,如果您具有在后台线程和该托管对象上创建托管对象的本机代码,并且该线程上的某些内容抛出本机异常,那么托管异常处理程序可能无法被调用(例如Dispose
)因为操作系统只会终止线程,所以可以处理的任何东西都不再可访问。
但是,是的,这些陈述实际上是等同的,并且在合理范围内finally
块将被执行并且Dispose
将被调用。
答案 5 :(得分:-1)
首先,我想提醒您使用语句不能用于所有类型。这只能用于实现 IDisposable 接口的类型,该接口具有自动处理对象的功能。这在您提到的第二份文件中出现
C#还包含using语句,它以方便的语法为IDisposable对象提供类似的功能。
这意味着如果发生未处理的异常,则清理对象由类型的Dispose()方法处理(这是使用语句文档时给出的)
进入查询,即使你的finally块(生成)不能保证运行未处理的异常,对象dispose操作也会在运行时由.Net CLR处理
希望这能清除你的怀疑