实现C#yield语句的算法
-
02-07-2019 - |
题
我想自己弄明白但是我想知道粗略地将带有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语句,为每个语句分配一个数字(一个状态值),并创建一个“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;
}
}
}
然后将方法体从原始方法移动到“闭合”内部的方法中。调用MoveNext,它返回一个bool,并实现IEnumerable.MoveNext。
对任何本地人的任何访问都通过“this”进行路由,对任何类成员的任何访问都通过this.originalThis进行路由。
任何“收益率回报率”被翻译成:
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”被称为。
然而,重要的是要指出C#编译器使用的转换不是执行此操作的最佳方法。当试图使用“产量”时,它的性能很差。使用递归算法。有一篇好文章概述了一种更好的方法:
http://research.microsoft.com/en-us /projects/specsharp/iterators.pdf
如果你还没读过,那值得一读。
其他提示
刚发现这个问题 - 我最近在上写了一篇文章。我将不得不将这里提到的其他链接添加到文章中......
Raymond chen回答这个问题; http://blogs.msdn.com/b/ oldnewthing /存档/ 2008/08/12 / 8849519.aspx
(编辑指向系列的第1部分,而不是第4部分)