Yield 关键字的神奇之处

发布于 2024-10-08 00:22:09 字数 809 浏览 0 评论 0原文

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

  } 

What made it possible under the compiler's hood toexecute the index ++以及在技术上从函数返回后 while 循环中的其余代码?

Ok, as I was poking around with building a custom enumerator, I had noticed this behavior that concerns the yield

Say you have something like this:

  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;
                }
        }

  } 

What makes it possible underneath the compiler's hood to execute the index ++ and the rest of the code in the while loop after you technically do a return from the function?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

未央 2024-10-15 00:22:09

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

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

The compiler rewrites the code into a state machine. The single method you wrote is split up into different parts. Each time you call MoveNext (either implicity or explicitly) the state is advanced and the correct block of code is executed.

Suggested reading if you want to know more details:

携余温的黄昏 2024-10-15 00:22:09

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

从语言规范来看:

10.14 迭代器

10.14.4 枚举器对象

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

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

• 它实现了 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;
            }
        }
    }
}

The compiler generates a state-machine on your behalf.

From the language specification:

10.14 Iterators

10.14.4 Enumerator objects

When a function member returning an
enumerator interface type is
implemented using an iterator block,
invoking the function member does not
immediately execute the code in the
iterator block. Instead, an enumerator
object is created and returned. This
object encapsulates the code specified
in the iterator block, and execution
of the code in the iterator block
occurs when the enumerator object’s
MoveNext method is invoked. An
enumerator object has the following
characteristics:

• It implements
IEnumerator and IEnumerator, where
T is the yield type of the iterator.

• It implements System.IDisposable.

• It is initialized with a copy of the
argument values (if any) and instance
value passed to the function member.

• It has four potential states,
before, running, suspended, and after,
and is initially in the before state.

An enumerator object is typically an
instance of a compiler-generated
enumerator class that encapsulates the
code in the iterator block and
implements the enumerator interfaces,
but other methods of implementation
are possible. If an enumerator class
is generated by the compiler, that
class will be nested, directly or
indirectly, in the class containing
the function member, it will have
private accessibility, and it will
have a name reserved for compiler use
(§2.4.2).

To get an idea of this, here's how Reflector decompiles your class:

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;
            }
        }
    }
}
定格我的天空 2024-10-15 00:22:09

产量是魔法。

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

阅读此内容进行介绍。

编辑:这是错误的。链接已更改,请再次检查是否曾经有过。

Yield is magic.

Well, not really. The compiler generates a full class to generate the enumeration that you're doing. It's basically sugar to make your life simpler.

Read this for an intro.

EDIT: Wrong this. Link changed, check again if you have once.

千鲤 2024-10-15 00:22:09

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

以简单的方式实现迭代器

进一步了解解释请参见 Marc Gravell 的回答:

有人可以揭开yield关键字的神秘面纱吗?

That's one of the most complex parts of the C# compiler. Best read the free sample chapter of Jon Skeet's C# in Depth (or better, get the book and read it :-)

Implementing iterators the easy way

For further explanations see Marc Gravell's answer here:

Can someone demystify the yield keyword?

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文