【问题标题】:Why is the execution order of inner 'finally' and outer 'when' swapped in C# 6.0?为什么在 C# 6.0 中内部'finally' 和外部'when' 的执行顺序交换了?
【发布时间】:2016-12-07 19:21:03
【问题描述】:

我看过这个例子:

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

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

【问题讨论】:

  • @RahulTripathi 不,我的问题是关于 C# 6.0 中交换的执行顺序,这个问题在 6.0 之前的原版 C# 中有直接执行顺序
  • 这是 when 子句如何工作的必然结果。 CLR 无法确定 finally 块需要执行什么,直到 之后 它已经确定了哪个 catch 子句将处理异常。只有这样它才能开始展开堆栈,同时执行 finally 块。 when 表达式可能是无意的副作用,这就是为什么在 v6 之前它被排除在 C# 语言之外的原因。
  • @HansPassant 为什么,具体来说,这是真的:“CLR 无法弄清楚 finally 块需要执行什么,直到它弄清楚什么 catch 子句将处理异常。”?跨度>
  • 就像踩着自行车不知道要去哪里。您无法决定是左转还是右转。
  • 这是避免when 子句中副作用的一个很好的理由。 ;)

标签: c# c#-6.0


【解决方案1】:

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

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

发生异常时,CLI 会在数组中搜索第一个受保护的块

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

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

如果找到匹配项,CLI 将堆栈返回到刚刚找到的点,但这次调用 finally 和错误处理程序。然后它启动相应的异常处理程序。

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

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

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

编辑:

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

【讨论】:

  • 哇。事实证明,我不知道最终在 C# 中是如何工作的。似乎通过投票,很多人都这样做了。你知道为什么 C# 团队决定这样实现它,而不是你在第一段中是如何描述的吗?
  • @Archeg -- 我仍在苦苦挣扎的一件事是你的论点,即这在 C# 6.0 中以某种方式发生了变化......你是说这不是它以前的行为吗那个?
  • @Archeg 嗯,在某些时候,通读 CLR 和 C# 的整个规范会变得非常有用 - 你会学到很多在运行中甚至不一定能区分的东西-the-mill 编码(直到发生这样的事情:))。而且这些规范写得很好,也包含了很多基本原理,所以它们也很好地洞察了“他们在想什么?”
  • @Archeg - 它几乎不是 C# 甚至 .NET - Windows SEH 也遵循这个两遍过程来定位处理程序并然后展开。
  • 我想我有一些想法为什么,但如果你要提到“不是 100% 保证”的部分,你应该详细说明或至少链接到另一个问题或一些文档。
【解决方案2】:

无论是否引发异常,finally 块总是会执行。

finally 块执行:

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

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

【讨论】:

  • 我不明白这是如何回答这个问题的。当问题非常具体并且实际上是关于 when 时,您提供了一些关于 finally 块的一般解释。
猜你喜欢
  • 2012-04-07
  • 2013-07-26
  • 2013-12-30
  • 2011-10-29
  • 1970-01-01
  • 2019-05-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多