实现C#yield语句的算法

时间:2020-03-06 14:42:27  来源:igfitidea点击:

我很想自己弄清楚它,但是我大概想知道将带有yield语句的函数转换为枚举器的状态机的算法是什么?例如,如何将其翻转:

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;
}

当然,结果可能会完全不同,具体取决于原始代码。

解决方案

雷蒙德·陈(Raymond chen)回答了这个问题。 http://blogs.msdn.com/b/oldnewthing/archive/2008/08/12/8849519.aspx

(经编辑以指向系列的第1部分,而不是第4部分)

我们正在查看的特定代码示例涉及一系列转换。
请注意,这是对该算法的大概描述。编译器使用的实际名称及其生成的确切代码可能有所不同。然后,想法是一样的。

第一个转换是" 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标签"。

第三次转换将方法主体中的所有局部变量和函数参数提升到一个称为闭包的对象中。

给定示例中的代码,该代码类似于以下内容:

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的方法,该方法返回布尔值,并实现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"时的语句。

重要的是要指出,但是,Ccompiler使用的转换并不是实现此目的的最佳方法。尝试将" yield"与递归算法一起使用时,它的性能很差。这里有一篇很好的论文,概述了一种更好的方法:

http://research.microsoft.com/en-us/projects/specsharp/iterators.pdf

如果我们还没有读过,那值得一读。

刚发现这个问题,我最近在上面写了一篇文章。不过,我必须将此处提到的其他链接添加到文章中...