Литмир - Электронная Библиотека
A
A

// Вручную работать с IEnumerator.

IEnumerator carEnumerator = carLot.GetEnumerator();

carEnumerator.MoveNext();

Car myCar = (Car)i.Current;

Console.WriteLine("{0} is going {1} MPH", myCar.PetName, myCar.CurrentSpeed);

Тем не менее, если вы предпочитаете скрыть функциональность

IEnumerable
на уровне объектов, то просто задействуйте явную реализацию интерфейса:

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

IEnumerator IEnumerable.GetEnumerator()

  => return carArray.GetEnumerator();

В результате обычный пользователь объекта не обнаружит метод

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

Построение итераторных методов с использованием ключевого слова yield

Существует альтернативный способ построения типов, которые работают с циклом

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

public class Garage : IEnumerable

{

  ...

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

  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
.

Защитные конструкции с использованием локальных функций (нововведение в версии 7.0)

До первого прохода по элементам (или доступа к любому элементу) никакой код в методе

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

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

GetEnumerator()
:

public IEnumerator GetEnumerator()

{

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

  // метод MoveNext().

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

  foreach (Car c in carArray)

  {

    yield return c;

  }

}

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

using System.Collections;

...

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

Garage carLot = new Garage();

IEnumerator carEnumerator = carLot.GetEnumerator();

Console.ReadLine();

Код выполнится только после вызова

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

176
{"b":"847442","o":1}