Подтвердить что ты не робот

Как можно перечислить урожай?

Я играл с yield и IEnumerable, и мне теперь любопытно, почему и как работает следующий фрагмент:

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>?

4b9b3361

Ответ 1

При использовании yield 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__2? Это создается на основе двух yield return s.

Ответ 2

Когда компилятор видит yield return или yield break, он принимает эту функцию и скрывает логику в классе, который реализует конечный автомат. Затем экземпляр этого класса возвращается при вызове метода.

С# In Depth содержит раздел о том, как выглядит код.

Ответ 3

Это генератор. Я не могу сказать, как работает компилятор, но когда вы это делаете:

yield return x;

Вы не оставите функцию подобно классическому возврату, вместо этого вы вернете значение, а затем продолжите выполнение функции.

Если я хорошо помню, есть небольшая разница между реальным перечислимым и этим, если вы не используете метод преобразования своего генератора в истинный перечислимый (в зависимости от того, что вы пытаетесь выполнить), вы можете иметь некоторые проблемы.