Создание перечислимых типов (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.