我很想自己弄明白,但我想知道将带有 yield 语句的函数转换为枚举器状态机的大致算法是什么?例如,C# 如何转换:
IEnumerator<string> strings(IEnumerable<string> args)
{ IEnumerator<string> enumerator2 = getAnotherEnumerator();
foreach(var arg in arg)
{ enumerator2.MoveNext();
yield return arg+enumerator.Current;
}
}
进入这个:
bool MoveNext()
{ switch (this.state)
{
case 0:
this.state = -1;
this.enumerator2 = getAnotherEnumerator();
this.argsEnumerator = this.args.GetEnumerator();
this.state = 1;
while (this.argsEnumerator.MoveNext())
{
this.arg = this.argsEnumerator.Current;
this.enumerator2.MoveNext();
this.current = this.arg + this.enumerator2.Current;
this.state = 2;
return true;
state1:
this.state = 1;
}
this.state = -1;
if (this.argsEnumerator != null) this.argsEnumerator.Dispose();
break;
case 2:
goto state1;
}
return false;
}
当然,根据原始代码,结果可能完全不同。
最佳答案
您正在查看的特定代码示例涉及一系列转换。 请注意,这是对算法的近似描述。编译器使用的实际名称和它生成的确切代码可能不同。然而,想法是一样的。
第一个转换是“foreach”转换,它转换这段代码:
foreach (var x in y)
{
//body
}
进入这段代码:
var enumerator = y.GetEnumerator();
while (enumerator.MoveNext())
{
var x = enumerator.Current;
//body
}
if (y != null)
{
enumerator.Dispose();
}
第二个转换找到函数体中所有的 yield return 语句,为每个语句分配一个数字(状态值),并在 yield 之后创建一个“goto label”。
第三个转换将方法体中的所有局部变量和函数参数提升到一个称为闭包的对象中。
鉴于您示例中的代码,它看起来类似于:
class ClosureEnumerable : IEnumerable<string>
{
private IEnumerable<string> args;
private ClassType originalThis;
public ClosureEnumerator(ClassType origThis, IEnumerable<string> args)
{
this.args = args;
this.origianlThis = origThis;
}
public IEnumerator<string> GetEnumerator()
{
return new Closure(origThis, args);
}
}
class Closure : IEnumerator<string>
{
public Closure(ClassType originalThis, IEnumerable<string> args)
{
state = 0;
this.args = args;
this.originalThis = originalThis;
}
private IEnumerable<string> args;
private IEnumerator<string> enumerator2;
private IEnumerator<string> argEnumerator;
//- Here ClassType is the type of the object that contained the method
// This may be optimized away if the method does not access any
// class members
private ClassType originalThis;
//This holds the state value.
private int state;
//The current value to return
private string currentValue;
public string Current
{
get
{
return currentValue;
}
}
}
然后方法主体从原始方法移动到“Closure”中称为 MoveNext 的方法,该方法返回 bool,并实现 IEnumerable.MoveNext。 对任何本地人的任何访问都通过“this”进行路由,对任何类成员的任何访问都通过 this.originalThis 进行路由。
任何“yield return expr”都被翻译成:
currentValue = expr;
state = //the state number of the yield statement;
return true;
任何 yield break 语句都被翻译成:
state = -1;
return false;
函数末尾有一个“隐式”yield break 语句。 然后在查看状态编号并跳转到关联标签的过程的开头引入 switch 语句。
然后将原始方法翻译成如下内容:
IEnumerator<string> strings(IEnumerable<string> args)
{
return new ClosureEnumerable(this,args);
}
事实上,方法的状态都被压入一个对象,而 MoveNext 方法使用一个 switch 语句/状态变量,这使得迭代器的行为就像控制被传递回最后一个点之后的点一样下次调用“MoveNext”时返回“yield return”语句。
但是,必须指出,C# 编译器使用的转换并不是执行此操作的最佳方法。当尝试将“yield”与递归算法一起使用时,它的性能不佳。这里有一篇很好的论文概述了执行此操作的更好方法:
http://research.microsoft.com/en-us/projects/specsharp/iterators.pdf
如果您还没有读过,值得一读。
关于c# - C# yield 语句的实现算法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/131871/