Чтение онлайн

ЖАНРЫ

ЯЗЫК ПРОГРАММИРОВАНИЯ С# 2005 И ПЛАТФОРМА .NET 2.0. 3-е издание

Троелсен Эндрю

Шрифт:

// Этот интерфейс информирует вызывающую сторону о том,

// что элементы объекта перечислимы.

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.

Методы

итератора в C#

В .NET 1.x для того, чтобы пользовательские коллекции (такие, как Garage) допускали применение конструкции foreach в операциях, подобных перечислению, реализация интерфейса IEnumerable (и, как правило, интерфейса IEnumerator) была обязательной. В C# 2005 предлагается альтернативный вариант построения типов, позволяющих применение цикла foreach, – с помощью итераторов.

В упрощённой интерпретации итератор является членом, указывающим порядок возвращения внутренних элементов контейнера при их обработке с помощью foreach. И хотя метод итератора все равно должен называться GetEnumerator, а возвращаемое значение все равно должно иметь тип IEnumerator, при таком подходе ваш пользовательский класс уже не обязан реализовывать все ожидаемые интерфейсы.

public class Garage { // Без реализации IEnumerator!

 private Car[] carArray;

 …

 // Метод итератора.

 public IEnumerator GetEnumerator {

foreach (Car с in carArray) {

yield return c;

}

 }

}

Обратите внимание на то, что данная реализация GetEnumerator осуществляет "проход" по вложенным элементам, используя внутреннюю логику foreach, и возвращает объекты Car вызывающей стороне, используя новую синтаксическую конструкцию yield return. Ключевое слово yield используется для того, чтобы указать значение (или значения), возвращаемые конструкции foreach вызывающей стороны. Когда в программе встречается оператор yield return, сохраняется текущая позиция, и именно с этой позиции выполнение будет продолжено при следующем вызове итератора.

Когда компилятор C# обнаруживает метод итератора, в рамках области видимости соответствующего типа (в данном случае это Garage) динамически генерируется вложенный класс. Этот автоматически сгенерированный класс реализует интерфейсы IEnumerable и IEnumerator и указывает необходимые параметры членов GetEnumerator, MoveNext, Reset и Current. Если теперь загрузить данное приложение в ildasm.exe, то будет видно, что внутренняя реализация GetEnumerator в объекте Garage использует сгенерированный компилятором тип (который в данном примере получает имя GetEnumeratord__0).

.method public hidebysig instance class [mscorlib] System.Collections.IEnumerator GetEnumerator cil managed {

 …

 newobj instance void CustomEnumeratorWithYield.Garage/ '‹GetEnumerator›d__0'::.ctor(int32)

 …

} // end of method Garage::GetEnumerator

Явно, что от предложенного здесь определения метода итератора мы не получим большой пользы, поскольку наш тип Garage изначально реализовывал GetEnumerator, ссылаясь на внутренний тип System.Array. Но синтаксис итератора C# может сэкономить немало времени при построении более "экзотических" пользовательских контейнеров (например, бинарных деревьев), где приходится вручную реализовать интерфейсы IEnumerator и IEnumerable. В любом случае программный код вызывающей стороны при взаимодействии с методом итератора с использованием foreach оказывается одинаковым.

Поделиться с друзьями: