// Возвратить IEnumerator объекта массива.

IEnumerator IEnumerable.GetEnumerator()

  => return carArray.GetEnumerator();

В результате обычный пользователь объекта не обнаружит метод GetEnumerator() в классе Garage, в то время как конструкция foreach при необходимости будет получать интерфейс в фоновом режиме.

<p id="AutBody_Root339">Построение итераторных методов с использованием ключевого слова yield</p>

Существует альтернативный способ построения типов, которые работают с циклом foreach, предусматривающий использование итераторов. Попросту говоря, итератор — это член, который указывает, каким образом должны возвращаться внутренние элементы контейнера во время обработки в цикле foreach. В целях иллюстрации создайте новый проект консольного приложения по имени CustomEnumeratorWithYield и вставьте в него типы Car, Radio и Garage из предыдущего примера (снова переименовав пространство имен согласно текущему проекту). Затем модифицируйте тип Garage:

public class Garage : IEnumerable

{

  ...

  // Итераторный метод.

  public IEnumerator GetEnumerator()

  {

    foreach (Car c in carArray)

    {

      yield return c;

    }

  }

}

Обратите внимание, что показанная реализация метода GetEnumerator() осуществляет проход по элементам с применением внутренней логики foreach и возвращает каждый объект Car вызывающему коду, используя синтаксис yield return. Ключевое слово yield применяется для указания значения или значений, которые подлежат возвращению конструкцией foreach вызывающему коду. При достижении оператора yield return текущее местоположение в контейнере сохраняется и выполнение возобновляется с этого местоположения, когда итератор вызывается в следующий раз.

Итераторные методы не обязаны использовать ключевое слово foreach для возвращения своего содержимого. Итераторный метод допускается определять и так:

public IEnumerator GetEnumerator()

{

   yield return carArray[0];

   yield return carArray[1];

   yield return carArray[2];

   yield return carArray[3];

}

В этой реализации обратите внимание на то, что при каждом своем прохождении метод GetEnumerator() явно возвращает вызывающему коду новое значение. В рассматриваемом примере поступать подобным образом мало смысла, потому что если вы добавите дополнительные объекты к переменной-члену carArray, то метод GetEnumerator() станет рассогласованным. Тем не менее, такой синтаксис может быть полезен, когда вы хотите возвращать из метода локальные данные для обработки посредством foreach.

<p id="AutBody_Root340">Защитные конструкции с использованием локальных функций (нововведение в версии 7.0)</p>

До первого прохода по элементам (или доступа к любому элементу) никакой код в методе 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();

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

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