将未处理的异常更改为finally块中的已处理异常

6 .net c# exception-handling language-lawyer

考虑这个程序:

using System;
static class Program {
  static void Main(string[] args) {
    try {
      try { throw new A(); }
      finally { throw new B(); }
    }
    catch (B) { }
    Console.WriteLine("All done!");
  }
}

class A : Exception { }

class B : Exception { }
Run Code Online (Sandbox Code Playgroud)

这里A抛出了一个没有处理程序的类型异常.在finally块中,B抛出了一个类型的异常,其中有一个处理程序.通常情况下,finally块中抛出的异常会获胜,但对于未处理的异常则不同.

调试时,调试器在A抛出时停止执行,并且不允许执行finally块.

如果不进行调试(从命令提示符单独运行它),将显示一条消息(打印,并显示崩溃对话框),其中包含未处理的异常,但在此之后,"全部完成!" 打印出来.

添加顶级异常处理程序时,除了重新抛出捕获的异常之外,一切都很好:没有意外消息,并且"全部完成!" 打印出来.

我理解这是如何发生的:在任何finally块执行之前确定异常是否有处理程序.这通常是可取的,并且当前行为是有意义的.finally无论如何,块通常不应该抛出异常.

但是这个其他Stack Overflow问题引用了C#语言规范并声称该finally块需要覆盖A异常.阅读规范,我同意这正是它所要求的:

  • 在当前函数成员中,try将检查包含抛出点的每个语句.对于每个语句S,从最里面的try语句开始,以最外层的try语句结束,评估以下步骤:
    • 如果tryS包含抛出点并且如果S有一个或多个catch子句,则检查catch子句[...]
    • 否则,如果try块或catchS包围投掷点并且如果Sfinally块,则控制转移到finally块.如果finally块抛出另一个异常,则终止当前异常的处理.否则,当控制到达finally块的结束点时,继续处理当前异常.
  • 如果当前函数调用中未找到异常处理程序,则终止函数调用,并发生以下之一:
    • [...]
  • 如果异常处理终止当前线程中的所有函数成员调用,指示该线程没有该异常的处理程序,则该线程本身终止.这种终止的影响是实现定义的.

一个例外是不被认为是未处理的,按照我对规范的阅读,直到所有的函数调用已经终止,直到函数调用未终止finally处理程序已经执行.

我在这里遗漏了什么,或者微软的C#实现是否与他们自己的规范不一致?

Pav*_*ets 1

我认为问题在于 .NET 异常处理是如何构建在结构化异常处理之上的,结构化异常处理对于在 finally 块中抛出的规则略有不同。

当异常 A 发生时,SEH 尝试找到第一个能够处理异常类型的处理程序,然后开始运行所有的 finally 块,展开到它,但基于 SEH 逻辑,不存在这样的处理程序,因此它会在 .NET 之前抱怨未处理的异常。执行自己的规则。

这解释了顶级处理程序(但仅限于可以处理异常类型 A 的处理程序)修复问题。

IL 本身看起来是有效的:

.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // Code size       49 (0x31)
  .maxstack  1
  IL_0000:  nop
  IL_0001:  nop
  .try
  {
    IL_0002:  nop
    .try
    {
      IL_0003:  nop
      IL_0004:  newobj     instance void A::.ctor()
      IL_0009:  throw
    }  // end .try
    finally
    {
      IL_000a:  nop
      IL_000b:  newobj     instance void B::.ctor()
      IL_0010:  throw
    }  // end handler
  }  // end .try
  catch B 
  {
    IL_0011:  pop
    IL_0012:  nop
    IL_0013:  ldstr      "B"
    IL_0018:  call       void [mscorlib]System.Console::WriteLine(string)
    IL_001d:  nop
    IL_001e:  nop
    IL_001f:  leave.s    IL_0021
  }  // end handler
  IL_0021:  nop
  IL_0022:  nop
  IL_0023:  nop
  IL_0024:  ldstr      "A"
  IL_0029:  call       void [mscorlib]System.Console::WriteLine(string)
  IL_002e:  nop
  IL_002f:  nop
  IL_0030:  ret
} // end of method Program::Main
Run Code Online (Sandbox Code Playgroud)

Mono也有同样的问题http://ideone.com/VVoPx6