【问题标题】:Why the compiler-generated state machine restores repeatedly the state to -1?为什么编译器生成的状态机反复将状态恢复为-1?
【发布时间】:2020-02-03 09:55:10
【问题描述】:

我试图了解迭代器在内部是如何工作的,以减轻我对线程安全的一些担忧。让我们考虑例如以下 简单的迭代器:

using System.Collections.Generic;

public class MyClass
{
    public static IEnumerable<int> MyMethod()
    {
        yield return 10;
        yield return 20;
        yield return 30;
    }
}

在将此代码复制粘贴到SharpLab.io 之后,我可以看到在后台创建的编译器生成的状态机。它是一个实现接口IEnumerable&lt;int&gt;IEnumerator&lt;int&gt;的类,并包含下面的MoveNext方法:

private bool MoveNext()
{
    switch (<>1__state)
    {
        default:
            return false;
        case 0:
            <>1__state = -1;
            <>2__current = 10;
            <>1__state = 1;
            return true;
        case 1:
            <>1__state = -1;
            <>2__current = 20;
            <>1__state = 2;
            return true;
        case 2:
            <>1__state = -1;
            <>2__current = 30;
            <>1__state = 3;
            return true;
        case 3:
            <>1__state = -1;
            return false;
    }
}

标识符&lt;&gt;1__state&lt;&gt;2__current 是此类的私有字段:

private int <>1__state;
private int <>2__current;

我注意到这段代码中有一个模式。首先将&lt;&gt;1__state 字段的值恢复为-1,然后将&lt;&gt;2__current 分配给下一个迭代值,然后将&lt;&gt;1__state 推进到下一个状态。我的问题是:&lt;&gt;1__state = -1; 行的目的是什么?我编译了这段代码(在痛苦地重命名所有非法标识符之后)并确认可以注释掉这一行而不影响类的功能。我不相信 C# 编译器团队只是忘记了这段看似毫无目的的代码。它的存在肯定是有目的的,我想知道这个目的是什么。

【问题讨论】:

  • 这里的&lt;&gt; 是什么?
  • @OlivierRogier 这是一个似乎对编译器生成的代码有效的前缀。根据 C# 规范肯定是无效的,所以我不得不重命名变量来编译这段代码。
  • 我不明白,因为&lt;&gt; 是菱形运算符:它允许对开放类型进行真正的泛型多态性,据我所知,它在 C# 中尚不可用。
  • @OlivierRogier 我认为尖括号在这里没有任何意义。添加可能是为了使标识符无效,以避免与用户代码冲突。
  • this.__state = -1; 的用途相当简单。这就是状态机的工作原理。因为状态机不知道您请求什么值,并且您的用户代码可能是一个非常慢的 Web 服务请求,所以它将状态设置为 -1 表示“我正忙于获取下一个值”。

标签: c# compilation iterator state-machine


【解决方案1】:

对于为什么需要一个状态变量并在每次输入 switch 语句时将其设置为 -1,没有一个明确的答案。但我可以想到一个你真的需要这个变量的例子。

就像我在评论部分所说,编译器不知道也不关心 2__current 做了什么。

下载文件可能是一个长时间运行的网络请求。它可能是计算的结果,也可能只是您的示例中的整数。但问题就在这里,因为编译器不知道你的代码做了什么,它可能会抛出异常。让我们看一个示例,如果您省略 _state 变量会发生什么情况,您会在尝试下载某些内容时遇到异常。

1) MoveNext is called.
2) this.<>2_current = WebRequest.GetFileAsync() throws HttpRequestException.
3) The exception is caught somewhere and the execution of the program is resumed.
4) The caller invokes MoveNext method.
5) this.<>2_current = WebRequest.GetFileAsync() throws HttpRequestException

所以在这种情况下,我们会陷入循环,因为只有在成功下载该数据后才会更改状态。

当我们引入 _state 变量时,结果看起来有很大不同。

1) MoveNext is called.
2) this.<>2_current = WebRequest.GetFileAsync() throws HttpRequestException.
3) The exception is caught somewhere and execution of the program is resumed.
4) The caller invokes MoveNext method.

5) Since there’s no switch case for -1, the default block is reached which informs about the end of a sequence.

【讨论】:

  • 有趣。所以这个赋值的目的是禁止重用遇到异常的枚举器。我想知道是否所有枚举器的行为方式都相同(例如 LINQ 枚举器),或者它只是编译器生成的。
  • 我的测试显示行为有所不同。我对此提出了一个新问题here
猜你喜欢
  • 2023-01-13
  • 1970-01-01
  • 1970-01-01
  • 2012-01-03
  • 1970-01-01
  • 2020-08-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多