yield 是如何枚举的?

How is yield an enumerable?

我一直在研究 yieldIEnumerable,现在我很好奇为什么或如何使用以下代码片段:

public class FakeList : IEnumerable<int>
{
    private int one;
    private int two;

    public IEnumerator<int> GetEnumerator()
    {
        yield return one;
        yield return two;
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

现在编译器如何转换这个:

public IEnumerator<int> GetEnumerator()
{
    yield return one;
    yield return two;
}

变成IEnumerator<int>?

使用yield return时,编译器会为您生成一个枚举器class。所以实际使用的代码比两个 return 语句复杂得多。编译器为您将所有必要的代码添加到 return 枚举器,它迭代 yield return.

的结果

这是从您的 FakeList.GetEnumerator():

生成的代码
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.CompilerServices;

public class FakeList : IEnumerable<int>, IEnumerable
{
    private int one;
    private int two;

    [IteratorStateMachine(typeof(<GetEnumerator>d__2))]
    public IEnumerator<int> GetEnumerator()
    {
        yield return this.one;
        yield return this.two;
    }

    IEnumerator IEnumerable.GetEnumerator() => 
        this.GetEnumerator();

    [CompilerGenerated]
    private sealed class <GetEnumerator>d__2 : IEnumerator<int>, IDisposable, IEnumerator
    {
        private int <>1__state;
        private int <>2__current;
        public FakeList <>4__this;

        [DebuggerHidden]
        public <GetEnumerator>d__2(int <>1__state)
        {
            this.<>1__state = <>1__state;
        }

        private bool MoveNext()
        {
            switch (this.<>1__state)
            {
                case 0:
                    this.<>1__state = -1;
                    this.<>2__current = this.<>4__this.one;
                    this.<>1__state = 1;
                    return true;

                case 1:
                    this.<>1__state = -1;
                    this.<>2__current = this.<>4__this.two;
                    this.<>1__state = 2;
                    return true;

                case 2:
                    this.<>1__state = -1;
                    return false;
            }
            return false;
        }

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

        [DebuggerHidden]
        void IDisposable.Dispose()
        {
        }

        int IEnumerator<int>.Current =>
            this.<>2__current;

        object IEnumerator.Current =>
            this.<>2__current;
    }
}

你看到<GetEnumerator>d__2class了吗?那是根据你的两个yield return生成的。

当编译器看到 yield returnyield break 时,它获取函数并将其逻辑转换为实现状态机的 class。然后在调用该方法时返回此 class 的一个实例。

C# In Depth 有一节介绍代码的外观。

这是一台发电机。 我不能说后面的编译器是如何工作的,但是当你这样做的时候:

yield return x;

您不会像经典 return 那样离开函数,而是 return 一个值,然后继续执行函数。

如果我没记错的话,真正的可枚举和这个之间有一点区别,如果你不使用方法将你的生成器转换成真正的可枚举(取决于你想要完成的事情)你可能会有一些麻烦。