使用yield return返回迭代器的方法的“延迟执行”的内幕

    最近看书时偶然发现的,使用yield return的方法有延迟执行的特性。

先看代码吧:

        static void Main(string[] args)
        {
            var theArray = GetPassedScores();
            //如果GetPassedScores被执行了,
            //应该能在此WriteLine之前看到输出的字符.
            Console.ReadKey();
            Console.WriteLine("Invoked...");

            foreach (int n in theArray)
            {
                Console.Write("{0},  ", n);
            }
            Console.WriteLine();

            Console.ReadKey();
        }// end Main.

        static IEnumerable GetPassedScores()
        {
            Console.WriteLine("Invoking GetPassedScores()...");
            int[] scores = {43, 78, 40, 2, 99, 100, 0, 30, 59, 64, 89};

            foreach (int s in scores)
            {
                if (s >= 60)
                {
                    yield return s;
                }
            }
        }


运行结果:



其中的技术内幕嘛,水平有限,查看IL代码看不懂,郁闷。

在此记下,有待研究<^_^>


//================================================

2013-2-28:好吧,改了标题,但不是太满意,实在不知道该怎么写标题,拗(ao)口了点,将就一下。

    终于明白一点了,查看IL代码,发现原来在Program类里多了个内嵌类型 d_0 。

使用Reflector 查看之,其内容如下:

[CompilerGenerated]
private sealed class d__0 : IEnumerable, IEnumerable, IEnumerator, IEnumerator, IDisposable
{
    // Fields
    private int <>1__state;
    private int <>2__current;
    public int[] <>7__wrap4;
    public int <>7__wrap5;
    private int <>l__initialThreadId;
    public int 5__2;
    public int[] 5__1;

    // Methods
    [DebuggerHidden]
    public d__0(int <>1__state);
    private void <>m__Finally3();
    private bool MoveNext();
    [DebuggerHidden]
    IEnumerator IEnumerable.GetEnumerator();
    [DebuggerHidden]
    IEnumerator IEnumerable.GetEnumerator();
    [DebuggerHidden]
    void IEnumerator.Reset();
    void IDisposable.Dispose();

    // Properties
    int IEnumerator.Current { [DebuggerHidden] get; }
    object IEnumerator.Current { [DebuggerHidden] get; }
}

    这是个迭代器,看看它实现的接口(如 IEnumerable......)。

    其实GetPassedScores方法就是返回这个类的一个实例 。把版本切换到 .NET 1.0,查看GetPassedScores方法的内容:

private static IEnumerable GetPassedScores()
{
    return new d__0(-2);
}


    那么方法体中 原来的代码 跑哪里去了? 自习查找,发现了线索:
 WriteLine("Invoking GetPassedScores()...");

    在生成的类的 MoveNext方法里,包含了生成筛选迭代器元素的代码:

private bool MoveNext()
{
    try
    {
        switch (this.<>1__state)
        {
            case 0:
                this.<>1__state = -1;
                Console.WriteLine("Invoking GetPassedScores()...");
                this.5__1 = new int[] { 0x2b, 0x4e, 40, 2, 0x63, 100, 0, 30, 0x3b, 0x40, 0x59 };
                this.<>1__state = 1;
                this.<>7__wrap4 = this.5__1;
                this.<>7__wrap5 = 0;
                while (this.<>7__wrap5 < this.<>7__wrap4.Length)
                {
                    this.5__2 = this.<>7__wrap4[this.<>7__wrap5];
                    if (this.5__2 < 60)
                    {
                        goto Label_00B1;
                    }
                    this.<>2__current = this.5__2;
                    this.<>1__state = 2;
                    return true;
                Label_00A9:
                    this.<>1__state = 1;
                Label_00B1:
                    this.<>7__wrap5++;
                }
                this.<>m__Finally3();
                break;

            case 2:
                goto Label_00A9;
        }
        return false;
    }
    fault
    {
        this.System.IDisposable.Dispose();
    }
}

    还有就是,注意到上面的迭代器的构造函数的调用了吗?参数是 ( -2 ),并且赋值给了 字段 <>1__state,从其构造函数的代码来看,这个字段似乎应该用于switch分支,但是没有对应 -2 的标签。

public d__0(int <>1__state)
{
    this.<>1__state = <>1__state;
    this.<>l__initialThreadId = Thread.CurrentThread.ManagedThreadId;
}

    再找一找,在 IEnumerable.GetEnumerator()的实现里发现了对 <>1__state == -2 的检测:

 IEnumerator IEnumerable.GetEnumerator()
    {
        if ((Thread.CurrentThread.ManagedThreadId == this.<>l__initialThreadId) && (this.<>1__state == -2))
        {
            this.<>1__state = 0;
            return this;
        }
        return new Program.d__0(0);
    }

    代码的大概意思就是:如果<>1__state != -2 就创建并返回新的实例

    而从代码上来看,一旦迭代器被使用一次之后,<>1__state 就应该不可能再变为 -2了,包括上面返回的新实例(其 参数是 0)。所以每次使用这个实例(严格点说,应该是访问到了其中的元素),都是使用 重新生成的一个新实例

    这感觉上和LINQ延迟查询 一样(也许就是一样的,因为LINQ和IEnumerable的关系在那放着。不过我水平有限,不好妄下断言,呵呵),即开篇的那个示例中的变量 theArray 好像不是个变量,倒像是个"委托",使用一次执行一次。

让我们多执行几次来看看吧,修改后的Main方法代码如下:

        static void Main(string[] args)
        {
            var theArray = GetPassedScores();
            //如果GetPassedScores被执行了,
            //应该能在此WriteLine之前看到输出的字符.
            Console.ReadKey();
            Console.WriteLine("Invoked...");

            Console.WriteLine("1");
            foreach (int n in theArray)
            {
                Console.Write("{0},  ", n);
            }
            Console.WriteLine();

            Console.WriteLine("2");
            foreach (int n in theArray)
            {
                Console.Write("{0},  ", n);
            }
            Console.WriteLine();

            Console.WriteLine("3");
            foreach (int n in theArray)
            {
                Console.Write("{0},  ", n);
            }
            Console.WriteLine();

            Console.ReadKey();
        }// end Main.

    可以看出,字符串“Invoking GetPassedScores()...”有3次输出,感觉像是执行了3次 GetPassedScores方法 其实是因为 每次访问迭代器中的元素 都是先新创建了一个迭代器 (d__0类型)


你可能感兴趣的:(学习杂记,C#)