Книга: ЯЗЫК ПРОГРАММИРОВАНИЯ С# 2005 И ПЛАТФОРМА .NET 2.0. 3-е издание
Методы итератора в C#
Методы итератора в 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 использует сгенерированный компилятором тип (который в данном примере получает имя ‹GetEnumerator›d__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 оказывается одинаковым.
static void Main(string[] args) {
Console.WriteLine("***** Забавы с методами итератора *****n");
Garage carLot = new Garage();
foreach (Car с in carLot) {
Console.WriteLine("{0} имеет скорость {1} км/ч", с.PetName, с.CurrrSpeed);
}
Console.ReadLine();
}
Исходный код. Проект CustomEnumeratorWifhYield размещен в подкаталоге, соответствующем главе 7.
- 1.2.6. Циклы и ветвление
- 1.5.4. Рубизмы и идиомы
- 8.2.5. Обход хэша
- 8.3.7. Объекты-генераторы
- 11.4.2. Итераторы
- Применение итераторов
- Linux и творческая интеллигенция
- 3. Лекция: Элементы функционального программирования.
- Циклы
- 8.3.6. Энумераторы
- Совет 2. Остерегайтесь иллюзий контейнерно-независимого кода
- Совет 28. Научитесь использовать функцию base