Книга: C# 4.0: полное руководство

Применение итераторов

Применение итераторов

Как следует из предыдущих примеров, реализовать интерфейсы IEnumerator и IEnumerable нетрудно. Но еще проще воспользоваться итератором, который представляет собой метод, оператор или аксессор, возвращающий по очереди члены совокупности объектов от ее начала и до конца. Так, если некоторый массив состоит из пяти элементов, то итератор данного массива возвратит все эти элементы по очереди. Реализовав итератор, можно обращаться к объектам определяемого пользователем класса в цикле foreach.

Обратимся сначала к простому примеру итератора. Приведенная ниже программа является измененной версией предыдущей программы, в которой вместо явной реализации интерфейсов IEnumerator и IEnumerable применяется итератор.

// Простой пример применения итератора.
using System;
using System.Collections;
class MyClass {
  char[] chrs = { 'A', 'B', 'C', 'D' };
  // Этот итератор возвращает символы из массива chrs.
  public IEnumerator GetEnumerator() {
    foreach (char ch in chrs)
      yield return ch;
  }
}
class ItrDemo {
  static void Main() {
    MyClass mc = new MyClass();
    foreach (char ch in mc)
      Console.Write(ch + " ");
    Console.WriteLine();
  }
}

При выполнении этой программы получается следующий результат.

А В С D

Как видите, содержимое массива mc.chrs перечислено.

Рассмотрим эту программу более подробно. Во-первых, обратите внимание на то, что в классе MyClass не указывается IEnumerator в качестве реализуемого интерфейса. При создании итератора компилятор реализует этот интерфейс автоматически. И во-вторых, обратите особое внимание на метод GetEnumerator(), который ради удобства приводится ниже еще раз.

// Этот итератор возвращает символы из массива chrs.
public IEnumerator GetEnumerator() {
  foreach(char ch in chrs)
    yield return ch;
}

Это и есть итератор для объектов класса MyClass. Как видите, в нем явно реализуется метод GetEnumerator(), определенный в интерфейсе IEnumerable. А теперь перейдем непосредственно к телу данного метода. Оно состоит из цикла foreach, в котором возвращаются элементы из массива chrs. И делается это с помощью оператора yield return. Этот оператор возвращает следующий объект в коллекции, которым в данном случае оказывается очередной символ в массиве chrs. Благодаря этому средству обращение к объекту mc типа MyClass организуется в цикле foreach внутри метода Main().

Обозначение yield служит в языке C# в качестве контекстного ключевого слова. Это означает, что оно имеет специальное назначение только в блоке итератора. А вне этого блока оно может быть использовано аналогично любому другому идентификатору.

Следует особо подчеркнуть, что итератор не обязательно должен опираться на массив или коллекцию другого типа. Он должен просто возвращать следующий элемент из совокупности элементов. Это означает, что элементы могут быть построены динамически с помощью соответствующего алгоритма. В качестве примера ниже приведена версия предыдущей программы, в которой возвращаются все буквы английского алфавита, набранные в верхнем регистре. Вместо массива буквы формируются в цикле for.

// Пример динамического построения значений,
// возвращаемых по очереди с помощью итератора.
using System;
using System.Collections;
class MyClass {
  char ch = 'A';
  // Этот итератор возвращает буквы английского
  // алфавита, набранные в верхнем регистре.
  public IEnumerator GetEnumerator() {
    for (int i = 0; i < 26; i++)
      yield return (char)(ch + i);
  }
}
class ItrDemo2 {
  static void Main() {
    MyClass me = new MyClass();
    foreach (char ch in me)
      Console.Write(ch + " ");
    Console.WriteLine();
  }
}

Вот к какому результату приводит выполнение этой программы.

A B C D E F G H I J K L M N O P Q R S T U V W X Y Z

Прерывание итератора

Для преждевременного прерывания итератора служит следующая форма оператора yield.

yield break;

Когда этот оператор выполняется, итератор уведомляет о том, что достигнут конец коллекции. А это, по существу, останавливает сам итератор.

Приведенная ниже программа является версией предыдущей программы, измененной с целью отобразить только первые десять букв английского алфавита.

// Пример прерывания итератора.
using System;
using System.Collections;
class MyClass {
  char ch = 'A';
  // Этот итератор возвращает первые 10 букв английского алфавита,
  public IEnumerator GetEnumerator() {
    for (int i = 0; i < 26; i++) {
      if (i == 10) yield break; // прервать итератор преждевременно
      yield return (char)(ch + i);
    }
  }
}
class ItrDemo3 {
  static void Main() {
    MyClass mc = new MyClass();
    foreach (char ch in mc)
      Console.Write(ch + " ");
    Console.WriteLine();
  }
}

Эта программа дает следующий результат.

A B C D E F G H I J

Применение нескольких операторов yield

В итераторе допускается применение нескольких операторов yield. Но каждый такой оператор должен возвращать следующий элемент в коллекции. В качестве примера рассмотрим следующую программу.

// Пример применения нескольких операторов yield.
using System;
using System.Collections;
class MyClass {
  // Этот итератор возвращает буквы А, В, С, D и Е.
  public IEnumerator GetEnumerator() {
    yield return 'A';
    yield return 'B';
    yield return 'C';
    yield return 'D';
    yield return 'E';
  }
}
class ItrDemo5 {
  static void Main() {
    MyClass mc = new MyClass();
    foreach (char ch in mc)
      Console.Write(ch + " ");
    Console.WriteLine();
  }
}

Ниже приведен результата выполнения этой программы.

А В С D Е

В данной программе внутри метода GetEnumerator() выполняются пять операторов yield. Следует особо подчеркнуть, что они выполняются по очереди и каждый раз, когда из коллекции получается очередной элемент. Таким образом, на каждом шаге цикла foreach в методе Main() возвращается только один символ.

Создание именованного итератора

В приведенных выше примерах был продемонстрирован простейший способ реализации итератора. Но ему имеется альтернатива в виде именованного итератора. В данном случае создается метод, оператор или аксессор, возвращающий ссылку на

объект типа IEnumerable. Именно этот объект используется в коде для предоставления итератора. Именованный итератор представляет собой метод, общая форма которого приведена ниже:

public IEnumerable имя_итератора (список_параметров) {
  // ...
  yield return obj;
}

где имя_итератора обозначает конкретное имя метода; список_параметров — от нуля до нескольких параметров, передаваемых методу итератора; obj — следующий объект, возвращаемый итератором. Как только именованный итератор будет создан, его можно использовать везде, где он требуется, например для управления циклом foreach.

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

// Использовать именованные итераторы.
using System;
using System.Collections;
class MyClass {
  char ch = 'A';
  // Этот итератор возвращает буквы английского алфавита,
  // начиная с буквы А и кончая указанным конечным пределом
  public IEnumerable MyItr(int end) {
    for (int i = 0; i < end; i++)
      yield return (char)(ch + i);
  }
  // Этот итератор возвращает буквы в заданных пределах
  public IEnumerable MyItr(int begin, int end) {
    for (int i = begin; i < end; i++)
      yield return (char)(ch + i);
  }
}
class ItrDemo4 {
  static void Main() {
    MyClass mc = new MyClass();
    Console.WriteLine("Возвратить по очереди первые 7 букв:");
    foreach (char ch in mc.MyItr(7))
      Console.Write(ch + " ");
    Console.WriteLine("n");
    Console.WriteLine("Возвратить по очереди буквы от F до L:");
    foreach (char ch in mc.MyItr(5, 12))
      Console.Write(ch + " ");
    Console.WriteLine();
  }
}

Эта программа дает следующий результат.

Возвратить по очереди первые 7 букв:
А В С D Е F G
Возвратить по очереди буквы от F до L:
F G Н I J К L

Создание обобщенного итератора

В приведенных выше примерах применялись необобщенные итераторы, но, конечно, ничто не мешает создать обобщенные итераторы. Для этого достаточно возвратить объект обобщенного типа IEnumerator<T> или IEnumerable<T>. Ниже приведен пример создания обобщенного итератора.

// Простой пример обобщенного итератора,
using System;
using System.Collections.Generic;
class MyClass<T> {
  T[] array;
  public MyClass(T[] a) {
    array = a;
  }
  // Этот итератор возвращает символы из массива chrs.
  public IEnumerator<T> GetEnumerator() {
    foreach (T obj in array)
      yield return obj;
  }
}
class GenericItrDemo {
  static void Main() {
    int[] nums = { 4, 3, 6, 4, 7, 9 };
    MyClass<int> mc = new MyClass<int>(nums);
    foreach (int x in mc)
      Console.Write(x + " ");
    Console.WriteLine();
    bool[] bVals = { true, true, false, true };
    MyClass<bool> mc2 = new MyClass<bool>(bVals);
    foreach (bool b in mc2)
      Console.Write(b + " ");
    Console.WriteLine();
  }
}

Вот к какому результату приводит выполнение этой программы.

4 3 6 4 7 9
True True False True

В данном примере массив, состоящий из возвращаемых по очереди объектов, передается конструктору класса MyClass. Тип этого массива указывает в качестве аргумента типа в конструкторе класса MyClass.

Метод GetEnumerator() оперирует данными обобщенного типа Т и возвращает перечислитель типа IEnumerator<T>. Следовательно, итератор, определенный в классе MyClass, способен перечислять данные любого типа.

Оглавление книги


Генерация: 1.641. Запросов К БД/Cache: 3 / 1
поделиться
Вверх Вниз