Главная

Популярная публикация

Научная публикация

Случайная публикация

Обратная связь

ТОР 5 статей:

Методические подходы к анализу финансового состояния предприятия

Проблема периодизации русской литературы ХХ века. Краткая характеристика второй половины ХХ века

Ценовые и неценовые факторы

Характеристика шлифовальных кругов и ее маркировка

Служебные части речи. Предлог. Союз. Частицы

КАТЕГОРИИ:






Интерфейс перечислимости IEnumerable




 

У этого интерфейса всего один метод - GetEnumerator(). У метода нет никаких входных аргументов, так что, кажется, организовать перечислимость объектов класса достаточно просто - написать реализацию одного метода. Это действительно просто, но требует понимания процесса перечислимости. Первая сложность состоит в том, что метод GetEnumerator синтаксически определен следующим образом:

IEnumerator GetEnumerator()

Это означает, что в результате вызова метода должен возвращаться интерфейсный объект, принадлежащий интерфейсу IEnumerator - еще одному интерфейсу, связанному с перечислимостью. Метод GetEnumerator требует создания объекта перечислителя - объекта, реализующего методы интерфейса IEnumerator. У интерфейса IEnumerator несколько методов, и в совокупности именно они и позволяют организовать процесс перечисления.

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

К счастью, во многих ситуациях есть более простой путь. Пусть нам нужно организовать перечислимость объектов некоторого класса. У этого класса есть поля, являющиеся объектами, для которых перечислимость уже обеспечена, например, одним из полей класса является массив. Все классы массивы независимо от типа элементов реализуют перечислимость элементов массива, и для них определен метод GetEnumerator. Тогда метод GetEnumerator, который необходимо создать для класса, будет сводиться к вызову метода GetEnumerator для поля класса, представляющего собой перечислимый контейнер элементов. В этом случае все будет совсем просто.

Давайте построим пример, моделирующий такой подход. Добавим в наш проект новый класс, одним из полей которого будет массив объектов Person. Организуем перечислимость в этом классе, сводящуюся к перечислимости персон массива.

/// <summary>

/// Класс - наследник интерфейса IEnumerable,

/// допускающий перечислимость объектов.

/// Перечислимость сводится к перечислимости персон,

/// заданных полем container

/// </summary>

class Persons:IEnumerable

{

protected int size;

protected Person[] container;

Random rnd = new Random();

/// <summary>

/// Конструктор по умолчанию.

/// Создает массив из 10 персон

/// </summary>

public Persons()

{

size = 10;

container = new Person[size];

FillContainer();

}

/// <summary>

/// Конструктор. Создает массив заданной размерности

/// Создает его элементы, используя рандомизацию.

/// </summary>

/// <param name="size">размерность массива</param>

public Persons(int size)

{

this.size = size;

container = new Person[size];

FillContainer();

}

/// <summary>

/// Конструктор, которому передается массив персон

/// </summary>

/// <param name="container"> массив Person</param>

public Persons(Person[] container)

{

this.container = container;

size = container.Length;

}

/// <summary>

/// Заполнение массива person

/// </summary>

void FillContainer()

{

for(int i = 0; i <size; i++)

{

int num = rnd.Next(3*size);

int age = rnd.Next(27, 46);

container[i] = new Person("агент_" + num, age);

}

}

}

Созданный класс Persons устроен просто. У него есть поле, названное container и представляющее собой массив с элементами класса Person. Набор конструкторов класса позволяет передать классу массив персон либо поручить самому классу его формирование, используя метод FillContainer. Поскольку класс объявлен наследником интерфейса IEnumerable, необходимо реализовать метод GetEnumerator, что обеспечит перечислимость объектов класса и даст возможность использовать цикл for each при работе с объектами класса. В данном случае реализовать метод интерфейса несложно, и вот как выглядит его реализация:

/// <summary>

/// Реализация метода интерфейса IEnumerable

/// Сводится к вызову соответствующего метода

/// для поля container - массива,

/// для которого этот метод реализован в библиотеке FCL

/// </summary>

/// <returns>перечислитель - интерфейсный объект</returns>

public IEnumerator GetEnumerator()

{

return container.GetEnumerator();

}

На описание метода в теге summary потребовалось больше времени и строчек текста, чем на тело метода, состоящее из одной строчки. Но на описание не стоит жалеть усилий! Задача решена, и можно попробовать протестировать работу с объектами класса. Добавим в класс Testing соответствующий метод:

public void TestEnumeration()

{

int size = 10;

Persons agents = new Persons(size);

foreach (Person agent in agents)

Console.WriteLine(agent.ToString());

}

В тесте создается объект класса Persons, и в цикле foreach объекты перебираются, возвращая каждый раз очередной объект класса Person. Метод ToString, определенный в классе Person, позволяет выводить информацию об объектах. Результаты работы теста показаны на рис. 5.8.

Рис. 5.8. Перечислимость и метод GetEnumerator

Итераторы

Нам удалось достаточно просто организовать перечислимость в классе за счет того, что в классе был определен контейнер со встроенным методом GetEnumerator. А что, если такого контейнера нет, или таких контейнеров несколько и хотелось бы организовать перечисление по каждому из контейнеров? Для решения подобных задач в язык C#, начиная с версии 2.0, встроен мощный механизм итераторов, существенно облегчающих задачу перечислимости объектов класса и открывающих новые возможности, которые нельзя реализовать только за счет наследования интерфейса IEnumerable.

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

yield return <выражение>;

При выполнении итератора автоматически создается контейнер, в который добавляется объект при каждом выполнении оператора yield. Добавляемый в контейнер элемент определяется выражением оператора yield. Порядок выполнения операторов yield определяет порядок перечислимости элементов контейнера. Оператор yield может быть задан в форме

yield break;

В такой форме он сигнализирует об окончании заполнения контейнера элементами.

 

Итераторов в классе может быть несколько. Каждый из них создает свой вид перечислимости объектов класса. Итератору не требуется, чтобы в классе существовал контейнер со встроенным методом перечислимости, хотя итератор может использовать контейнер, изменяя порядок перечислимости, заданный для контейнера по умолчанию.

Давайте добавим в класс Persons два итератора. Первый из этих итераторов будет использовать существующий в классе контейнер, изменяя заданный порядок перечислимости элементов на обратный.

/// <summary>

/// Перечисление элементов массива container

/// в обратном порядке

/// </summary>

/// <returns>интерфейсный объект</returns>

public IEnumerable ReverseIterator()

{

for (int i = container.Length - 1; i >= 0; i--)

yield return container[i];

}

Оператор yield, работающий в цикле, первым возвратит последний элемент массива, а последним - первый.

Создадим еще один итератор, никак не связанный с элементами массива поля container. Зададим перечисление, содержащее имена известных языков программирования:

public enum ProgrammingLanguages

{

Fortran, Algol, Cobol, Simula, Pascal,

Ada, C, CPlusPlus, Eiffel, Java, CSharp

}

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

/// <summary>

/// Итератор. Создает объект перечисления

/// и строит контейнер,

/// помещая в него элементы перечисления

/// </summary>

/// <returns>интерфейсный объект</returns>

public IEnumerable LanguagesIterator()

{

ProgrammingLanguages lang;

int i = 0;

while (true)

{

lang = (ProgrammingLanguages)i;

yield return lang;

if (lang.ToString() == "CSharp") yield break;

i++;

}

}

 

В цикле оператор yield возвращает элементы перечисления до тех пор, пока не встретится элемент со значением "CSharp". Давайте теперь протестируем то, что у нас получилось. Добавим несколько строк кода в уже рассмотренный метод TestEnumeration:

Console.WriteLine("Обратный порядок перечисления");

foreach(Person agent in agents.ReverseIterator())

Console.WriteLine(agent.ToString());

Console.WriteLine("Перечисление языков программирования");

foreach (ProgrammingLanguages lang in

agents.LanguagesIterator())

Console.WriteLine(lang.ToString());

Обратите внимание: когда клиент организует цикл foreach, он указывает не только имя объекта класса, но и имя итератора, организующего перечислимость. Результаты работы теста показаны на рис. 5.9.

Рис. 5.9. Перечислимость и итераторы






Не нашли, что искали? Воспользуйтесь поиском:

vikidalka.ru - 2015-2024 год. Все права принадлежат их авторам! Нарушение авторских прав | Нарушение персональных данных