Чтение онлайн

ЖАНРЫ

Язык программирования C#9 и платформа .NET5
Шрифт:

{

IEnumerator GetEnumerator;

}

Как видите, метод

GetEnumerator
возвращает ссылку на еще один интерфейс по имени
System.Collections.IEnumerator
, обеспечивающий инфраструктуру, которая позволяет вызывающему коду обходить внутренние объекты, содержащиеся в совместимом с
IEnumerable
контейнере:

// Этот интерфейс позволяет вызывающему коду получать элементы контейнера.

public interface IEnumerator

{

bool MoveNext ; // Переместить вперед внутреннюю позицию курсора.

object Current { get;} //
Получить текущий элемент

// (свойство только для чтения).

void Reset ; // Сбросить курсор в позицию перед первым элементом.

}

Если вы хотите обновить тип

Garage
для поддержки этих интерфейсов, то можете пойти длинным путем и реализовать каждый метод вручную. Хотя вы определенно вольны предоставить специализированные версии методов
GetEnumerator
,
MoveNext
,
Current
и
Reset
, существует более легкий путь. Поскольку тип
System.Array
(а также многие другие классы коллекций) уже реализует интерфейсы
IEnumerable
и
IEnumerator
, вы можете просто делегировать запрос к
System.Array
следующим образом (обратите внимание, что в файл кода понадобится импортировать пространство имен
System.Collections
):

using System.Collections;

...

public class Garage : IEnumerable

{

// System.Array уже реализует IEnumerator!

private Car[] carArray = new Car[4];

public Garage

{

carArray[0] = new Car("FeeFee", 200);

carArray[1] = new Car("Clunker", 90);

carArray[2] = new Car("Zippy", 30);

carArray[3] = new Car("Fred", 30);

}

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

public IEnumerator GetEnumerator

=> carArray.GetEnumerator;

}

После такого изменения тип

Garage
можно безопасно использовать внутри конструкции
foreach
. Более того, учитывая, что метод
GetEnumerator
был определен как открытый, пользователь объекта может также взаимодействовать с типом
IEnumerator
:

// Вручную работать с 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

{

...

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

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("This won't get called");

foreach (Car c in carArray)

{

yield return c;

}

}

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

Поделиться с друзьями: