Чудеса ключевого слова yield - PullRequest
6 голосов
/ 16 декабря 2010

Хорошо, когда я возился с созданием собственного перечислителя, я заметил это поведение, касающееся 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 после того, как вы технически выполните возврат из функции?

Ответы [ 5 ]

9 голосов
/ 16 декабря 2010

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

Рекомендуется прочитать, если вы хотите узнать больше:

4 голосов
/ 16 декабря 2010

Компилятор создает конечный автомат от вашего имени.

Из спецификации языка:

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;
            }
        }
    }
}
2 голосов
/ 16 декабря 2010

Это одна из самых сложных частей компилятора C #.Лучше всего прочитать бесплатный образец главы Джона Скита C # в глубине (или лучше, взять книгу и прочитать ее: -)

Легко реализовать итераторыway

Дополнительные объяснения см. в ответе Марка Гравелла:

Может ли кто-нибудь демистифицировать ключевое слово yield?

2 голосов
/ 16 декабря 2010

Вот отличная серия блогов (от ветерана Microsoft Раймонда Чена), в которых подробно рассказывается, как работает 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

2 голосов
/ 16 декабря 2010

Урожай волшебен.

Ну, не совсем.Компилятор генерирует полный класс для генерации перечисления, которое вы делаете.Это в основном сахар, чтобы сделать вашу жизнь проще.

Читать это для вступления.

РЕДАКТИРОВАТЬ: Неправильно это.Ссылка изменена, проверьте еще раз, если у вас есть.

...