yield关键字的奇妙之处

6

好的,当我在构建自定义枚举器时,我注意到了与yield相关的行为问题。

假设你有像这样的内容:

  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个回答

9

没错,状态机,我读到了。但是它生成的那种代码是什么,状态机在其中扮演了什么角色?伪代码将会非常受欢迎。 - dexter
@Max Malygin:我提供的文章链接 http://csharpindepth.com/Articles/Chapter6/IteratorBlockImplementation.aspx 展示了生成的代码。 - Mark Byers
还要查看Matt Greer的答案:https://dev59.com/cVLTa4cB1Zd3GeqPcrLh#4455832 他也找到了系列的第四部分。 - Mark Byers
谢谢你的提醒。这个链接可能更好,因为它按顺序给出了它们:http://blogs.msdn.com/b/ericlippert/archive/tags/iterators/ - Eric Lippert

4
编译器会为您生成一个状态机。
从语言规范来看:
10.14 迭代器
10.14.4 枚举对象
当使用迭代器块实现返回枚举器接口类型的函数成员时,调用函数成员不会立即执行迭代器块中的代码。相反,将创建并返回一个枚举器对象。此对象封装了迭代器块中指定的代码,并在调用枚举器对象的 MoveNext 方法时执行迭代器块中的代码。枚举器对象具有以下特征:
• 它实现 IEnumerator 和 IEnumerator,其中 T 是迭代器的 yield 类型。
• 它实现 System.IDisposable。
• 它是使用传递给函数成员的参数值(如果有)和实例值进行初始化的副本。
• 它有四个潜在状态:before、running、suspended 和 after,并最初处于 before 状态。
枚举器对象通常是编译器生成的枚举器类的实例,该类封装了迭代器块中的代码并实现了枚举器接口,但也可以使用其他实现方法。如果编译器生成了枚举器类,则该类将被嵌套在包含函数成员的类中,它将具有私有可访问性,并且它将具有保留供编译器使用的名称(§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

yield是一种神奇的技术。

其实不然。编译器会生成一个完整的类来生成你正在进行的枚举。这基本上是为了让你的生活更简单而提供的语法糖。

点击这里阅读介绍。

编辑:链接有误,请检查并重新查看。


@Max - 根据您点击链接的时间,现在可能已经不同了。我最初发布了错误的链接。 - Donnie


2

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接