До первого прохода по элементам (или доступа к любому элементу) никакой код в методе GetEnumerator не выполняется. Таким образом, если до выполнения оператора yield возникает условие для исключения, то оно не будет сгенерировано при первом вызове метода, а лишь во время первого вызова MoveNext.

Чтобы проверить это, модифицируйте GetEnumerator:

public IEnumerator GetEnumerator

{

  // Исключение не сгенерируется до тех пор, пока не будет вызван

  // метод MoveNext.

  throw new Exception("This won't get called");

  foreach (Car c in carArray)

  {

    yield return c;

  }

}

Если функция вызывается, как показано далее, и больше ничего не делается, тогда исключение никогда не сгенерируется:

using System.Collections;

...

Console.WriteLine("***** Fun with the Yield Keyword *****\n");

Garage carLot = new Garage;

IEnumerator carEnumerator = carLot.GetEnumerator;

Console.ReadLine;

Код выполнится только после вызова MoveNext и сгенерируется исключение. В зависимости от нужд программы это может быть как вполне нормально, так и нет. Ваш метод GetEnumerator может иметь защитную конструкцию, которую необходимо выполнить при вызове метода в первый раз. В качестве примера предположим, что список формируется из базы данных. Вам может понадобиться организовать проверку, открыто ли подключение к базе данных, во время вызова метода, а не при проходе по списку. Или же может возникнуть потребность в проверке достоверности входных параметров метода Iterator, который рассматривается далее.

Вспомните средство локальных функций версии C# 7, представленное в главе 4; локальные функции — это закрытые функции, которые определены внутри других функций. За счет перемещения yield return внутрь локальной функции, которая возвращается из главного тела метода, операторы верхнего уровня (до возвращения локальной функции) выполняются немедленно. Локальная функция выполняется при вызове MoveNext.

Приведите метод к следующему виду:

public IEnumerator GetEnumerator

{

  // Это исключение сгенерируется немедленно

  throw new Exception("This will get called");

  return ActualImplementation;

  // Локальная функция и фактическая реализация IEnumerator

  IEnumerator ActualImplementation

  {

    foreach (Car c in carArray)

    {

      yield return c;

    }

  }

}

Ниже показан тестовый код:

Console.WriteLine("***** Fun with the Yield Keyword *****\n");

Garage carLot = new Garage;

try

{

  // На этот раз возникает ошибка

  var carEnumerator = carLot.GetEnumerator;

}

catch (Exception e)

{

  Console.WriteLine($"Exception occurred on GetEnumerator");

}

Console.ReadLine;

В результате такого обновления метода GetEnumerator исключение генерируется незамедлительно, а не при вызове MoveNext.

<p id="AutBody_Root341">Построение именованного итератора</p>

Также интересно отметить, что ключевое слово yield формально может применяться внутри любого метода независимо от его имени. Такие методы (которые официально называются именованными итераторами) уникальны тем, что способны принимать любое количество аргументов. При построении именованного итератора имейте в виду, что метод будет возвращать интерфейс IEnumerable, а не ожидаемый совместимый с IEnumerator тип. В целях иллюстрации добавьте к типу Garage следующий метод (использующий локальную функцию для инкапсуляции функциональности итерации):

public IEnumerable GetTheCars(bool returnReversed)

{

  // Выполнить проверку на предмет ошибок

  return ActualImplementation;

  IEnumerable ActualImplementation

  {

    // Возвратить элементы в обратном порядке.

    if (returnReversed)

    {

      for (int i = carArray.Length; i != 0; i--)

      {

        yield return carArray[i - 1];

      }

    }

    else

    {

Перейти на страницу:

Похожие книги