using System.Collections;
namespace CustomEnumerator
{
// Garage содержит набор объектов Car.
public class Garage
{
private Car[] carArray = new Car[4];
// При запуске заполнить несколькими объектами Car.
public Garage()
{
carArray[0] = new Car("Rusty", 30);
carArray[1] = new Car("Clunker", 55);
carArray[2] = new Car("Zippy", 30);
carArray[3] = new Car("Fred", 30);
}
}
}
В идеальном случае было бы удобно проходить по внутренним элементам объекта
Garage
с применением конструкции
foreach
как в ситуации с массивом значений данных:
using System;
using CustomEnumerator;
// Код выглядит корректным...
Console.WriteLine("***** Fun with IEnumerable / IEnumerator *****\n");
Garage carLot = new Garage();
// Проход по всем объектам Car в коллекции?
foreach (Car c in carLot)
{
Console.WriteLine("{0} is going {1} MPH",
c.PetName, c.CurrentSpeed);
}
Console.ReadLine();
К сожалению, компилятор информирует о том, что в классе Garage не реализован метод по имени
GetEnumerator()
, который формально определен в интерфейсе
IEnumerable
, находящемся в пространстве имен
System.Collections
.
На заметку! В главе 10 вы узнаете о роли обобщений и о пространстве имен
System.Collections.Generic
. Как будет показано, это пространство имен содержит обобщенные версии интерфейсов
IEnumerable/IEnumerator
, которые предлагают более безопасный к типам способ итерации по элементам.
Классы или структуры, которые поддерживают такое поведение, позиционируются как способные предоставлять вызывающему коду доступ к элементам, содержащимся внутри них (в рассматриваемом примере самому ключевому слову
foreach
). Вот определение этого стандартного интерфейса:
// Данный интерфейс информирует вызывающий код о том,
// что элементы объекта могут перечисляться
public interface IEnumerable
{
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
: