为什么在C#6.0中交换内部'finally'和外部'when'的执行顺序?

Arc*_*heg 45 c# 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");
    }
}
Run Code Online (Sandbox Code Playgroud)

这会产生下一个输出:

Start
EvaluatesTo: True
Inner Finally
Catch
Outer Finally
Run Code Online (Sandbox Code Playgroud)

这对我来说听起来很奇怪,而且我正在寻找一个很好的解释这个命令,将它包含在我脑海中.我以前期待finally块执行when:

Start
Inner Finally
EvaluatesTo: True
Catch
Outer Finally
Run Code Online (Sandbox Code Playgroud)

文档说明这个执行顺序是正确的,但它没有详细说明为什么这样做,以及这里执行顺序的确切规则是什么.

Lua*_*aan 38

您可能已经被告知,当发生异常处理时,每个方法都是单独考虑的.也就是说,由于你的内部方法有一个try...finally,任何异常都会先触发finally,然后它会"查找" try更高的值.事实并非如此.

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

发生异常时,CLI将在阵列中搜索第一个受保护的块

  • 保护包含当前指令指针区域的区域
  • 是一个catch处理程序块
  • 谁的过滤器希望处理异常

如果在当前方法中找不到匹配项,则搜索调用方法,依此类推.如果未找到匹配项,CLI将转储堆栈跟踪并中止该程序.

如果找到匹配项,CLI会将堆栈移回到刚定位的点,但这次调用finally和fault处理程序.然后它启动相应的异常处理程序.

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

  1. 寻找一个保护块- trySomeOperation
  2. 它有一个catch处理程序块吗?没有.
  3. 在调用方法中查找受保护的块 - tryinMain
  4. 它有一个catch处理程序块吗?是!
  5. 过滤器是否希望处理异常?评估过滤器(免责声明:这并不意味着保护块中的所有过滤器将始终被评估 - 如果过滤器没有副作用,没有问题,当然它实际上不应该这样),结果是是.
  6. 走回堆栈并执行所有finally和故障处理程序
    1. finallySomeOperation

当然,finallyin Main不是它的一部分 - 它将在执行离开受保护块时执行,而不管异常.

编辑:

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