работающая схема возврата урожая

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

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

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

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

Пояснения:

(1) Обычно у нас будет один допустимый оператор return внутри функции. Как C # обрабатывает, когда присутствует несколько операторов yield return, yield return,...?

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


person user193276    schedule 20.10.2009    source источник
comment
Отвечая на вопрос о книге: C# in Depth (Manning, Skeet), глава 6. Это бесплатный пример главы, в которой рассматриваются блоки итераторов. Это не совсем книга для начинающих по C# (далеко от этого), но вам будет сложно найти лучший справочник по этой теме.   -  person Marc Gravell    schedule 21.10.2009
comment
Если бы привилегии позволяли спросить: «Вы не написали ни одной книги?»   -  person user193276    schedule 21.10.2009
comment
Нет, я не видел. Я делаю корректуру для издателя, иногда пишу странные статьи и т. д. Но никаких моих книг.   -  person Marc Gravell    schedule 21.10.2009
comment
Я буду рад, если вы сделаете это, потому что я видел ваше объяснение для некоторых вопросов. Это соответствует уровню программиста начального уровня и ветерану.   -  person user193276    schedule 21.10.2009


Ответы (4)


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


Обратите внимание, что это также помогает, если вы понимаете, что 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; (или подобным)

Способ, которым компилятор C# делает это, очень сложен, но упрощает написание итераторов.

person Marc Gravell    schedule 20.10.2009
comment
Невероятно, что люди голосуют за это до того, как это на самом деле является полезным ответом. - person Joren; 20.10.2009
comment
@Joren: Вот насколько хорош Марк. ;) Отличный ответ, Марк! - person jrista; 20.10.2009
comment
Это очень просто: когда Марк говорит, что собирается что-то опубликовать, вы можете поспорить, что это будет правильно. - person kemiller2002; 20.10.2009
comment
Неотразимое объяснение и безупречные заметки. Как новичок, я впитал достаточно вещей от всех вас. Спасибо всем за заботу. :) - person user193276; 21.10.2009
comment
@Joren: это пример хорошо работающей системы репутации. Люди знают @Marc Gravell из его предыдущих сообщений, он говорит, что напишет это от руки, это хороший способ объяснить это, и потому что он делает это, это означает, что нам не нужно это делать. - person Hamish Smith; 21.10.2009
comment
Привет всем, пожалуйста, посоветуйте мне какую-нибудь книгу для начинающих, чтобы обогатить мои знания в C#. - person user193276; 21.10.2009
comment
@Hamish - я очень доверяю таким, как Марк, которые, как я видел, регулярно дают очень хорошие ответы, но все же я бы никогда не проголосовал за ответ, основанный на чем-то другом, кроме конкретных достоинств этого ответа. Но я полагаю, мне не следует говорить слишком много мета здесь. :) - person Joren; 21.10.2009
comment
@generix: C# in Depth (Manning, Skeet), глава 6. Это бесплатный образец главы, в котором рассматриваются блоки итераторов. Это не совсем книга для начинающих по C# (далеко от этого), но вам будет сложно найти лучший справочник по этой теме. - person Marc Gravell; 21.10.2009
comment
@ Джорен прав. Я бы тоже не проголосовал. Но если бы я увидел (например), что у Джона есть аналогичный маркер прогресса, я бы не стал тратить много усилий на его дублирование; это было моим намерением. - person Marc Gravell; 21.10.2009

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

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

person Arsen Mkrtchyan    schedule 20.10.2009

Проще говоря, блоки итераторов (или методы с операторами 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.

person Bryan Menard    schedule 20.10.2009

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

person Charles Bretana    schedule 20.10.2009