yield关键字的奇迹

时间:2010-12-15 22:54:34

标签: c# enumeration yield yield-return

好的,当我在构建自定义枚举器时,我注意到这种与产量

相关的行为

说你有这样的事情:

  public class EnumeratorExample 
  {

        public static IEnumerable<int> GetSource(int startPoint) 
        {
                int[] values = new int[]{1,2,3,4,5,6,7};
                Contract.Invariant(startPoint < values.Length);
                bool keepSearching = true;
                int index = startPoint;

                while(keepSearching) 
                {
                      yield return values[index];
                      //The mind reels here
                      index ++ 
                      keepSearching = index < values.Length;
                }
        }

  } 

在技术上从函数返回后,是什么使编译器的底层可以执行index ++和while循环中的其余代码?

5 个答案:

答案 0 :(得分:9)

编译器将代码重写为状态机。您编写的单个方法分为不同的部分。每次调用MoveNext(隐含或明确)时,状态都会提前,并执行正确的代码块。

如果您想了解更多详情,建议阅读:

答案 1 :(得分:4)

编译器代表您生成状态机。

来自语言规范:

  

10.14迭代器

     

10.14.4枚举器对象

     

当一个函数成员返回时   枚举器接口类型是   使用迭代器块实现,   调用函数成员不会   立即执行中的代码   迭代器块。相反,一个普查员   创建并返回对象。这个   object封装指定的代码   在迭代器块中,执行   迭代器块中的代码   在枚举器对象的时候发生   调用MoveNext方法。一个   枚举器对象具有以下内容   特性:

     

•实施   IEnumerator和IEnumerator,在哪里   T是迭代器的yield类型。

     

•它实现了System.IDisposable。

     

•初始化了一份副本   参数值(如果有)和实例   传递给函数成员的值。

     

•它有四种潜在的状态,   之前,跑步,停赛和之后,   并且最初处于之前状态。

     

枚举器对象通常是一个   编译器生成的实例   封装了的枚举器类   迭代器块中的代码和   实现枚举器接口,   但其他实施方法   是可能的。如果是枚举器类   由编译器生成   class将直接嵌套或   间接地,在包含的类中   功能成员,它会有   私人无障碍,它会   有一个名称保留供编译器使用   (第2.4.2节)。

为了弄清楚这一点,这里是Reflector如何反编译你的类:

public class EnumeratorExample
{
    // Methods
    public static IEnumerable<int> GetSource(int startPoint)
    {
        return new <GetSource>d__0(-2) { <>3__startPoint = startPoint };
    }

    // Nested Types
    [CompilerGenerated]
    private sealed class <GetSource>d__0 : IEnumerable<int>, IEnumerable, IEnumerator<int>, IEnumerator, IDisposable
    {
        // Fields
        private int <>1__state;
        private int <>2__current;
        public int <>3__startPoint;
        private int <>l__initialThreadId;
        public int <index>5__3;
        public bool <keepSearching>5__2;
        public int[] <values>5__1;
        public int startPoint;

        // Methods
        [DebuggerHidden]
        public <GetSource>d__0(int <>1__state)
        {
            this.<>1__state = <>1__state;
            this.<>l__initialThreadId = Thread.CurrentThread.ManagedThreadId;
        }

        private bool MoveNext()
        {
            switch (this.<>1__state)
            {
                case 0:
                    this.<>1__state = -1;
                    this.<values>5__1 = new int[] { 1, 2, 3, 4, 5, 6, 7 };
                    this.<keepSearching>5__2 = true;
                    this.<index>5__3 = this.startPoint;
                    while (this.<keepSearching>5__2)
                    {
                        this.<>2__current = this.<values>5__1[this.<index>5__3];
                        this.<>1__state = 1;
                        return true;
                    Label_0073:
                        this.<>1__state = -1;
                        this.<index>5__3++;
                        this.<keepSearching>5__2 = this.<index>5__3 < this.<values>5__1.Length;
                    }
                    break;

                case 1:
                    goto Label_0073;
            }
            return false;
        }

        [DebuggerHidden]
        IEnumerator<int> IEnumerable<int>.GetEnumerator()
        {
            EnumeratorExample.<GetSource>d__0 d__;
            if ((Thread.CurrentThread.ManagedThreadId == this.<>l__initialThreadId) && (this.<>1__state == -2))
            {
                this.<>1__state = 0;
                d__ = this;
            }
            else
            {
                d__ = new EnumeratorExample.<GetSource>d__0(0);
            }
            d__.startPoint = this.<>3__startPoint;
            return d__;
        }

        [DebuggerHidden]
        IEnumerator IEnumerable.GetEnumerator()
        {
            return this.System.Collections.Generic.IEnumerable<System.Int32>.GetEnumerator();
        }

        [DebuggerHidden]
        void IEnumerator.Reset()
        {
            throw new NotSupportedException();
        }

        void IDisposable.Dispose()
        {
        }

        // Properties
        int IEnumerator<int>.Current
        {
            [DebuggerHidden]
            get
            {
                return this.<>2__current;
            }
        }

        object IEnumerator.Current
        {
            [DebuggerHidden]
            get
            {
                return this.<>2__current;
            }
        }
    }
}

答案 2 :(得分:2)

收益率很高。

嗯,不是真的。编译器生成一个完整的类来生成您正在执行的枚举。它可以让你的生活变得更加简单。

阅读this了解简介。

编辑:错了。链接已更改,如果您有一次,请再次检查。

答案 3 :(得分:2)

答案 4 :(得分:2)

这是C#编译器中最复杂的部分之一。最好阅读Jon Skeet的 C#深度的免费样本章节(或者更好,阅读本书并阅读: - )

  

<强> Implementing iterators the easy way

有关进一步说明,请参阅Marc Gravell的回答:

  

<强> Can someone demystify the yield keyword?