【问题标题】:The wonders of the yield keywordyield 关键字的奇妙之处
【发布时间】:2011-05-26 05:49:27
【问题描述】:

好的,当我在构建自定义枚举器时,我注意到了这种与 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;
                }
        }

  } 

在技术上从函数返回后,是什么让编译器能够在 while 循环中执行索引 ++ 和其余代码?

【问题讨论】:

    标签: c# enumeration yield yield-return


    【解决方案1】:

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

    如果您想了解更多详细信息,建议阅读:

    【讨论】:

    【解决方案2】:

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

    来自语言规范:

    10.14 迭代器

    10.14.4 枚举器对象

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

    • 它实现 IEnumerator 和 IEnumerator,其中 T 是迭代器的产量类型。

    • 它实现了 System.IDisposable。

    • 它使用 参数值(如果有)和实例 传递给函数成员的值。

    • 它有四种可能的状态, 之前、运行、暂停和之后, 并且最初处于之前的状态。

    枚举器对象通常是 编译器生成的实例 枚举器类,封装了 迭代器块中的代码和 实现枚举器接口, 但其他实现方法 是可能的。如果一个枚举器类 由编译器生成,即 类将直接嵌套或 间接地,在包含的类中 函数成员,它将具有 私人可访问性,它将 为编译器使用保留名称 (§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;
                }
            }
        }
    }
    

    【讨论】:

      【解决方案3】:

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

      Implementing iterators the easy way

      更多解释请参见 Marc Gravell 的回答:

      Can someone demystify the yield keyword?

      【讨论】:

        【解决方案4】:

        这是一个优秀的博客系列(来自微软资深人士 Raymond Chen),详细介绍了 yield 的工作原理:

        第 1 部分:http://blogs.msdn.com/b/oldnewthing/archive/2008/08/12/8849519.aspx
        第 2 部分:http://blogs.msdn.com/b/oldnewthing/archive/2008/08/13/8854601.aspx
        第 3 部分:http://blogs.msdn.com/b/oldnewthing/archive/2008/08/14/8862242.aspx
        第 4 部分:http://blogs.msdn.com/b/oldnewthing/archive/2008/08/15/8868267.aspx

        【讨论】:

          【解决方案5】:

          收益就是魔法。

          嗯,不是真的。编译器会生成一个完整的类来生成您正在执行的枚举。它基本上是让你的生活更简单的糖。

          阅读this 了解简介。

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

          【讨论】:

          • @Max - 根据您点击链接的时间,现在可能会有所不同。我最初发布了错误的。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2023-03-05
          • 1970-01-01
          • 2015-02-21
          • 1970-01-01
          • 1970-01-01
          • 2011-01-17
          • 1970-01-01
          相关资源
          最近更新 更多