异常处理(与文档/ try-finally与使用相矛盾)

时间:2017-02-15 13:21:12

标签: c# exception try-catch using

我以为我已经理解了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块永远不会执行,并且程序在没有附加调试器的情况下执行时会死掉。但这种情况并非如此;相反,"例外对话框"我们都知道得太清楚了,之后,"你好"出现对话框。

在考虑了一段时间之后,在阅读了文档,文章和问题之后,例如thisthat,很明显这个"异常对话框"由一个标准的异常处理程序生成,该处理程序内置在Application.Run()和其他常用的方法中,这些方法可以启动"你的程序,所以我不再想知道为什么运行finally块。

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

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

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

这意味着"你好"对话框应该出现之前的"例外对话框",不是吗?嗯,显然,反之亦然。有人可以详细说明吗?

6 个答案:

答案 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。

除了关闭计算机外,还有一些情况可以让操作系统终止您的应用程序 - 有效地将其关闭。在这些情况下,Disposefinally不会被调用。这些通常是非常严重的错误条件,如堆栈外内存不足。有一些复杂的情况可能会发生异常情况少于例外情况。例如,如果您具有在后台线程和该托管对象上创建托管对象的本机代码,并且该线程上的某些内容抛出本机异常,那么托管异常处理程序可能无法被调用(例如Dispose)因为操作系统只会终止线程,所以可以处理的任何东西都不再可访问。

但是,是的,这些陈述实际上是等同的,并且在合理范围内finally块将被执行并且Dispose将被调用。

答案 5 :(得分:-1)

首先,我想提醒您使用语句不能用于所有类型。这只能用于实现 IDisposable 接口的类型,该接口具有自动处理对象的功能。这在您提到的第二份文件中出现

  

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

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

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

希望这能清除你的怀疑