Создание перечислимых типов (Enumerable и IEnumerator)
Создание перечислимых типов (Enumerable и IEnumerator)
Чтобы перейти к иллюстрации процесса реализации существующих интерфейсов .NET, нужно выяснить роль IEnumerable и IEnumerator. Предположим, что у нас есть класс Garage (гараж), содержащий некоторый набор типов Car (см. главу б), хранимых в виде System.Array.
// Garage содержит набор объектов Car.
public class Garage {
private Car[] carArray;
// Начальное наполнение объектами Car.
public Garage() {
carArray = new Car[4];
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, используя конструкцию C# foreach.
// Это кажется разумным…
public class Program {
static void Main(string[] args) {
Garage carLot = new Garage();
// Для каждого объекта Car в коллекции?
foreach (Car c in carLot) {
Console.WriteLine("{0} имеет скорость {1} км/ч", с.PetName, с.CurrSpeed);)
}
}
Но, как это ни печально, компилятор сообщит вам, что класс Garage не реализует метод GetEnumerator(). Этот метод формально определен интерфейсом IEnumerable, находящимся в "недрах" пространства имен System.Collections. Объекты, поддерживающие соответствующий вариант поведения, декларируют, что они могут раскрыть содержащиеся в них элементы вызывающей стороне.
// Этот интерфейс информирует вызывающую сторону о том,
// что элементы объекта перечислимы.
public interface IEnumerable {
IEnumerator GetEnumerator();
}
Как видите, метод GetEnumerator() должен возвращать ссылку на другой интерфейс – интерфейс c именем 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, как показано ниже.
using System.Collections;
public class Garage: IEnumerable {
// В System.Array уже есть реализация IEnumerator!
private Car[] carArray;
public Cars() {
carArray = new Car[4];
carArray[0] = new Car("FeeFee", 200, 0);
carArray[l] = new Car("Clunker", 90, 0);
carArray[2] = new Car("Zippy, 30, 0);
carArray[3] = new Car("Fjred", 30, 0);}
public IEnumerator GetEnumerator() {
// Возвращает IEnumerator объекта массива.
return carArray.GetEnumerator();
}
}
Теперь, после модификации типа Garage, вы можете использовать этот тип в конструкции foreach без опасений. К тому же, поскольку метод GetEnumerator() определен, как открытый, пользователь объекта тоже может взаимодействовать с типом IEnumerator.
// Manually work with IEnumerator.
IEnumerator I = carLot.GetEnumerator();
i.MoveNext();
Car myCar = (Car)i.Current;
Console.WriteLine("{0} имеет скорость {1} км/ч", myCar.PetName, myCar.CurrSpeed);
Если вы предпочтете скрыть функциональные возможности IEnumerable на объектном уровне, то следует использовать явную реализацию интерфейса.
public IEnumerator IEnumerable.GetEnumerator() {
// Возвращает IEnumerator объекта массива.
return carArray.GetEnumerator();
}
Исходный код. Проект CustomEnumerator размещен в подкаталоге, соответствующем главе 7.