C# 为什么CLR使用双过程异常模型?

C# 为什么CLR使用双过程异常模型?,c#,exception,clr,C#,Exception,Clr,第四版C#Depth中有一章介绍了C#6中添加的异常过滤器。它描述了CLR的异常处理模型: 您可能已经习惯了CLR作为异常“冒泡”展开堆栈,直到捕获为止。更令人惊讶的是,这到底是怎么发生的。这个过程比使用两次传递模型所期望的要复杂得多。此模型使用以下步骤: 抛出异常,并开始第一次传递 CLR遍历堆栈,试图找到哪个catch块将处理异常。(我们将其称为处理捕捉块(handling catch block)作为速记,但这不是官方术语。) 仅考虑具有兼容异常类型的catch块 如果catch块具有异

第四版C#Depth中有一章介绍了C#6中添加的异常过滤器。它描述了CLR的异常处理模型:

您可能已经习惯了CLR作为异常“冒泡”展开堆栈,直到捕获为止。更令人惊讶的是,这到底是怎么发生的。这个过程比使用两次传递模型所期望的要复杂得多。此模型使用以下步骤:

  • 抛出异常,并开始第一次传递
  • CLR遍历堆栈,试图找到哪个catch块将处理异常。(我们将其称为处理捕捉块(handling catch block)作为速记,但这不是官方术语。)
  • 仅考虑具有兼容异常类型的catch块
  • 如果catch块具有异常过滤器,则执行该过滤器;如果筛选器返回false,此catch块将不会处理异常
  • 没有异常筛选器的catch块相当于具有返回true的异常筛选器的catch块
  • 现在已经确定了处理捕捉块,第二遍开始:
  • CLR将从引发异常的点开始,直到已确定的catch块为止,展开堆栈
  • 将执行展开堆栈时遇到的任何finally块。(这不包括与处理catch块关联的任何finally块。)
  • 执行处理捕获块
  • 执行与处理catch块关联的finally语句(如果有)
从C#开发人员的角度来看,两次传递语义与处理所有事情的单次传递语义之间的唯一区别似乎是异常过滤器与
finally
块执行之间的顺序-在两次传递语义中,所有过滤器在任何
finally
块执行之前进行评估。我想得越多,就越不明白为什么会这样决定

  • 执行单个过程听起来效率更高,因此我假设这将是默认的选择实现,除非有足够的理由选择另一个
  • 选择处理块后执行
    finally
    子句可能会导致控制流在过滤谓词无效后进入块。下面是一个简单的例子:
  • 公共C类
    {
    私有静态int状态=42;
    公共静态void Foo()
    {
    尝试
    {
    抛出新异常();
    }
    最后
    {
    国家=17;
    }
    }
    公共静态void Main()
    {
    尝试
    {
    Foo();
    }
    捕获(异常)时间(状态==42)
    {
    控制台写入线(状态);
    }
    }
    }
    
    这是违反直觉的,因为在为
    catch
    块编写代码时,很容易将此代码理解为“当我进入
    catch
    状态时,
    总是
    42
    ”,而实际语义是“当我进入
    catch
    块时,我知道当抛出捕获的异常时,
    状态是
    42
    ”。因此,行的文字读取不起作用-当
    catch
    异常
    状态等于
    42
    时,它不是”
    异常
    ,”
    捕获
    异常
    如果
    当它被抛出时
    状态
    等于
    42

  • 您可以“反转”这一点,并最终创建一个
    块,使较低的过滤器适用。因此,如果我们从第2点修改代码,使其在(State==17)
    时具有
    ,异常将不会被捕获,并将使应用程序崩溃,即使直觉上认为它“通过”“当
    状态
    等于
    17时,
    catch

  • 该模型的一个优点是所有
    finally
    块都独立于过滤谓词。因此,如果过滤器有任何副作用,我们可以保证在运行任何
    最终
    块之前,它们将自下而上顺序运行。但这是有争议的,因为带有副作用的过滤器似乎是对系统的滥用,而
    最后
    块是通常需要施加某种副作用才能清除的普通代码

  • 列出以上所有内容使我想到:

    • 我看不出这种模式比“更简单”的单通模式有什么好处;或

    • 有一个深层次的、技术上的原因与低级的东西有关,这就是为什么CLR必须这样才能工作


    我想知道这是哪一个,如果这是一个深刻的,技术上的原因,请给我一个“傻瓜”的概述。Jon Skeet在那一章中指出,“[该模型的起源]比我想尝试的更深入到CLR中”,因此我认为原因是技术性的和不明显的,但是如果能听到CLR基础设施专家的推理,那就太好了。

    尽管该工具的设置并不是为了利用双通道设计的所有潜在优势,在许多情况下,发生异常的地方的代码无法立即知道在调试器中检查或记录在执行到达异常处理程序之前“清理”的系统状态是否有用

    如果在某些上下文中调用了一个函数,例如从远程网站读取某些数据,代码预期该函数可能会失败(并捕获异常),而在某些上下文中,失败可能是意外的,如果在后一种情况下抛出异常,那么能够怀疑执行以检查套接字的状态可能很有用,而不必在预期失败的情况下暂停执行

    注意,这
    > 17