c# - C# yield 语句的实现算法

标签 c# algorithm compiler-construction iterator state-machine

我很想自己弄明白,但我想知道将带有 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/

相关文章:

c# - 在本地调试 C# AWS Lambda 函数

c# - using语句中TransactionScope在哪里完成?

javascript - d3.js 如何简化复杂路径 - 使用自定义算法

ios - LLVM、GCC 4.2 和 Apple LLVM 编译器 3.1 之间的区别

c# - 为泛型类型实现 ninject 提供程序

java - 将 'bits' 写入 C++ 文件流

algorithm - 多次生成质数

c++ - 在2D vector 上使用STL算法提取第一列

android - 有什么工具可以将 html5 游戏编译成本地移动设备

c# - 返回字符串时的编译器优化