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:
// Вручную работать с IEnumerator.
IEnumerator carEnumerator = carLot.GetEnumerator();
carEnumerator.MoveNext();
Car myCar = (Car)i.Current;
Console.WriteLine("{0} is going {1} MPH", myCar.PetName, myCar.CurrentSpeed);
Тем не менее, если вы предпочитаете скрыть функциональность IEnumerable на уровне объектов, то просто задействуйте явную реализацию интерфейса: