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();
(window.adrunTag = window.adrunTag || []).push({v: 1, el: 'adrun-4-390', c: 4, b: 390})
}
Как видите, метод 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: