为什么编译器生成的状态机反复将状态恢复为-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.
我试图了解迭代器在内部是如何工作的,以减轻我对线程安全的一些担忧。让我们考虑例如以下 简单迭代器:
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.