为什么在 C# 6.0 中 inner 'finally' 和 outer 'when' 的执行顺序交换了?

Why is the execution order of inner 'finally' and outer 'when' swapped in C# 6.0?

我看过这个例子:

static void Main(string[] args)
{
    Console.WriteLine("Start");
    try
    {
        SomeOperation();
    }
    catch (Exception) when (EvaluatesTo())
    {
        Console.WriteLine("Catch");
    }
    finally
    {
        Console.WriteLine("Outer Finally");
    }
}

private static bool EvaluatesTo()
{
    Console.WriteLine($"EvaluatesTo: {Flag}");
    return true;
}

private static void SomeOperation()
{
    try
    {
        Flag = true;
        throw new Exception("Boom");
    }
    finally
    {
        Flag = false;
        Console.WriteLine("Inner Finally");
    }
}

产生下一个输出:

Start
EvaluatesTo: True
Inner Finally
Catch
Outer Finally

这对我来说听起来很奇怪,我正在寻找对这个命令的一个很好的解释,以便在我的脑海中总结一下。我期待 finally 块在 when:

之前执行
Start
Inner Finally
EvaluatesTo: True
Catch
Outer Finally

文档上说这个执行顺序是正确的,但是并没有详细说明为什么会这样,这里执行顺序的规则是什么。

无论是否抛出异常,finally 块始终执行。

finally 块执行:

  • catch 块完成后
  • 由于跳转语句(例如,return 或 goto)控制离开 try 块后
  • try 块结束后

唯一可以打败 finally 块的是无限循环或进程突然发送。 finally 块有助于为程序添加确定性

您可能被告知,当发生异常处理时,每个方法都会被单独考虑。也就是说,由于您的内部方法有一个 try...finally,任何异常都会首先触发 finally,然后它会 "look" 触发更高的 try。这不是真的。

来自 CLR 的 ECMA 规范(ECMA-335,I.12.4.2.5 异常处理概述):

When an exception occurs, the CLI searches the array for the first protected block that

  • Protects a region including the current instruction pointer and
  • Is a catch handler block and
  • Whose filter wishes to handle the exception

If a match is not found in the current method, the calling method is searched, and so on. If no match is found the CLI will dump a stack trace and abort the program.

If a match is found, the CLI walks the stack back to the point just located, but this time calling the finally and fault handlers. It then starts the corresponding exception handler.

如您所见,该行为 100% 符合规范。

  1. SomeOperation
  2. 中寻找受保护的块 - try
  3. 它有捕获处理程序块吗?号
  4. 在调用方法中寻找受保护的块 - try in Main
  5. 它有捕获处理程序块吗?是的!
  6. 过滤器是否希望处理异常?过滤器被评估(免责声明:这并不意味着将始终评估受保护块中的所有过滤器 - 如果过滤器没有副作用,那没问题,它确实应该' t, 当然), 结果是.
  7. 回溯堆栈并执行所有finally和fault handlers
    1. finallySomeOperation

Main 中的 finally 不是其中的一部分,当然 - 它会在执行离开受保护块时执行,而不管异常。

编辑:

只是为了完整性 - 一直都是这样。唯一改变的是 C# 现在支持异常过滤器,它允许您观察执行顺序。 VB.NET 从版本 1 开始支持异常过滤器。