Рабочий график возврата урожая

Когда у меня есть кодовый блок

static void Main()
{

  foreach (int i in YieldDemo.SupplyIntegers())
  {
    Console.WriteLine("{0} is consumed by foreach iteration", i);
  }
}


 class YieldDemo
  {
    public static IEnumerable<int> SupplyIntegers()
     {
         yield return 1;
         yield return 2;
          yield return 3;
       }
   }

Могу ли я интерпретировать принцип возврата доходности как

  • Main() вызывает функцию SupplyIntegers()
  • |1| |2| |3| are stored in contiguous memory block.Pointer of "IEnumerator" Moves to |1|
  • Control возвращает from SupplyInteger() в Main().
  • Main() печатает значение
  • Указатель Переместится в | 2 | и т.д.

Разъяснения:

(1) Обычно у нас есть один действительный оператор return внутри функции. Как это сделать   С# обрабатывает, когда присутствуют несколько возвратов доходности, return return,......

(2) После того, как встречается возврат, нет возможности снова вернуться к управлению   SupplyIntegers(), если он разрешен, не будет ли доход снова начинаться с 1? Я имею в виду возврат доходности 1?

Ответ 1

Нет - далеко от него; Я напишу для вас длинную версию... это слишком шероховато!


Обратите внимание, что это также помогает, если вы понимаете, что foreach на самом деле:

using(var iterator = YieldDemo.SupplyIntegers().GetEnumerator()) {
    int i;
    while(iterator.MoveNext()) {
        i = iterator.Current;
         Console.WriteLine("{0} is consumed by foreach iteration", i);
    }
}

using System;
using System.Collections;
using System.Collections.Generic;
static class Program
{
    static void Main()
    {

        foreach (int i in YieldDemo.SupplyIntegers())
        {
            Console.WriteLine("{0} is consumed by foreach iteration", i);
        }
    }
}

 class YieldDemo
  {

    public static IEnumerable<int> SupplyIntegers()
     {
         return new YieldEnumerable();
       }
    class YieldEnumerable : IEnumerable<int>
    {
        public IEnumerator<int> GetEnumerator()
        {
            return new YieldIterator();
        }
        IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }
    }
    class YieldIterator : IEnumerator<int>
    {
        private int state = 0;
        private int value;
        public int Current { get { return value; } }
        object IEnumerator.Current { get { return Current; } }
        void IEnumerator.Reset() { throw new NotSupportedException(); }
        void IDisposable.Dispose() { }
        public bool MoveNext()
        {
            switch (state)
            {
                case 0: value = 1; state = 1;  return true;
                case 1: value = 2; state = 2;  return true;
                case 2: value = 3; state = 3; return true;
                default: return false;
            }
        }
    }
}

Как вы можете видеть, он создает конечный автомат в итераторе, а конечный автомат продвигается на MoveNext. Я использовал шаблон с полем state, так как вы можете видеть, как это будет работать для более сложных итераторов.

Важно:

  • любые переменные в вашем блоке итератора становятся полями на конечной машине
  • если у вас есть блок finally (включая using), он находится в Dispose()
  • части кода, ведущие к yield return, становятся case (примерно)
  • yield break становится state = -1; return false; (или аналогичным)

То, как компилятор С# делает это, очень сложно, но делает запись итераторов легкой.

Ответ 2

Это просто синтаксический сахар,.net генерирует класс IEnumerator для вас и реализует методы MoveNext, Current и Reset, чем генерирует класс IEnumarable GetEnumerator, из которого возвращается этот IEnumerator, вы можете видеть, что магические классы с помощью рефлектора .net или ildasm.

Также см. здесь

Ответ 3

Проще говоря, блоки итератора (или методы с операторами yield, если возможно) преобразуются компилятором в класс, сгенерированный компилятором. Этот класс реализует IEnumerator, а оператор yield преобразуется в "состояние" для этого класса.

Например, это:

yield return 1;
yield return 2;
yield return 3;

может превратиться в нечто похожее на:

switch (state)
{
    case 0: goto LABEL_A;
    case 1: goto LABEL_B;
    case 2: goto LABEL_C;
}
LABEL_A:
    return 1;
LABEL_B:
    return 2;
LABEL_C:
    return 3;

Блоки итераторов можно рассматривать как абстрактные государственные машины. Этот код будет вызываться методами IEnumerator.

Ответ 4

вкратце (в то время как yr ждет длинную версию marc), когда компилятор видит инструкции yield, за кулисами он создает новый экземпляр настраиваемого класса для вас, который реализует интерфейс под названием IEnumerator, который имеет методы Current() и MoveNext(), и отслеживает, где вы сейчас находитесь в процессе итерации... В приведенном выше примере в качестве примера он также будет отслеживать значения в списке, которые должны быть перечислены.

Ответ 5

Ты помнишь диктовку в школе? Предположим, у нас есть пятеро детей, которые приходят за диктацией английских слов. У нас есть список слов, написанных на доске. Дети не видят доску, но учителя ее видят. Обратите внимание, что слова должны быть указаны в том же порядке для всех детей. У каждого ребенка учится другой учитель.

Blackboard:
    Соединенные Штаты Америки
    Соединенное Королевство
    Индия
Вопросы
а. Кто такие клиенты?
б. Кто является поставщиком данных?
с. Кто диктует (Итерация или Перечислитель) сборник?
Ответы:
а. Дети
б. Доска объявлений
с.
Учитель

В терминах С#:

Blackboard - поставщик данных - IEnumerable
Учитель - Итератор - Государственный автомат
Дети - Клиент - ForEach