为什么编译器生成的状态机反复将状态恢复为-1?

Why the compiler-generated state machine restores repeatedly the state to -1?

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

using System.Collections.Generic;

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

在将这段代码复制粘贴到下面的 SharpLab.io. It is a class that implements the interfaces IEnumerable<int> and IEnumerator<int>, and contains the 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;
    }
}

标识符<>1__state<>2__current是这个class的私有字段:

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

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

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

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

下载文件的 Web 请求可能很长 运行ning。它可能是计算的结果,也可能只是您示例中的整数。但问题就在这里,因为编译器不知道你的代码做了什么,它可能会抛出异常。让我们看一个示例,说明如果您省略 _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.