Перебор объектов (интерфейс IEnumerable) и итераторы Оператор foreach является удобным средством перебора элементов объекта. Массивы и все стандартные коллекции библиотеки .NET позволяют выполнять такой перебор благодаря тому, что в них реализованы интерфейсы IEnumerable и IEnumerator. Для применения оператора foreach к пользовательскому типу данных требуется реализовать в нем эти интерфейсы. Интерфейс IEnumerable (перечислимый) определяет всего один метод – GetEnumerator, возвращающий объект типа IEnumerator (перечислитель), который можно использовать для просмотра элементов объекта. Интерфейс IEnumerator задает три элемента: · свойство Current, возвращающее текущий элемент объекта; · метод MoveNext, продвигающий перечислитель на следующий элемент объекта; · метод Reset, устанавливающий перечислитель в начало просмотра. Цикл foreach использует эти методы для перебора элементов, из которых состоит объект. Таким образом, если требуется, чтобы для перебора элементов класса мог применяться цикл foreach, необходимо реализовать четыре метода: GetEnumerator, Current, MoveNext и Reset. Это не интересная работа, а выполнять ее приходится часто, поэтому, начиная с версии 2.0, были введены средства, облегчающие выполнение перебора в объекте – итераторы. Итератор представляет собой блок кода, задающий последовательность перебора элементов объекта. На каждом проходе цикла foreach выполняется один шаг итератора, заканчивающийся выдачей очередного значения. Выдача значения выполняется с помощью ключевого слова yield. Рассмотрим создание итератора на примере (лістинг 9). Пусть требуется создать объект, содержащий боевую группу экземпляров типа Monster. Листинг9. Класс с итератором using System; using System.Collections; using System.Linq; using System.Text; namespace ConsoleApplication1 { class Monster { ... } class Daemon { ... } class Stado : IEnumerable // 1 { private Monster[] mas; private int n; public Stado() { mas = new Monster[10]; n = 0; } public IEnumerator GetEnumerator() { for ( int i = 0; i < n; ++i ) yield return mas[i]; // 2 } public void Add( Monster m ) { if ( n >= 10 ) return; mas[n] = m; ++n; } } class Class1 { static void Main() { Stado s = new Stado(); s.Add( new Monster() ); s.Add( new Monster("Вася") ); s.Add( new Daemon() ); foreach ( Monster m in s ) m.Passport(); } } } Все, что требуется сделать для поддержки перебора – указать, что класс реализует интерфейс IEnumerable (оператор 1), и описать итератор (оператор 2). Доступ к нему может быть осуществлен через методы MoveNext и Current интерфейса IEnumerator. Преимущество использования итераторов заключается в том, что для одного и того же класса можно задать различный порядок перебора элементов. В листинге 10 описаны две дополнительные стратегии перебора элементов класса Stado, введенного в листинге 9 – перебор в обратном порядке и выборка только тех объектов, которые являются экземплярами класса Monster. Листинг 10. Реализация нескольких стратегий перебора using System; using System.Collections; using System.Linq; using System.Text; namespace ConsoleApplication1 { class Monster { ... } class Daemon { ... } class Stado : IEnumerable { private Monster[] mas; private int n; public Stado() { mas = new Monster[10]; n = 0; } public IEnumerator GetEnumerator() { for ( int i = 0; i < n; ++i ) yield return mas[i]; } public IEnumerable Backwards() // в обратном порядке { for ( int i = n - 1; i >= 0; --i ) yield return mas[i]; } public IEnumerable MonstersOnly() // только монстры { for ( int i = 0; i < n; ++i ) if ( mas[i].GetType().Name == "Monster" ) yield return mas[i]; } public void Add( Monster m ) { if ( n >= 10 ) return; mas[n] = m; ++n; } } class Class1 { static void Main() { Stado s = new Stado(); s.Add( new Monster() ); s.Add( new Monster("Вася") ); s.Add( new Daemon() ); foreach ( Monster i in s ) i.Passport(); foreach ( Monster i in s.Backwards() ) i.Passport(); foreach ( Monster i in s.MonstersOnly() ) i.Passport(); } } } Итак, блок итератора синтаксически представляет собой обычный блок и может встречаться в теле метода, операции или части get свойства, если соответствующее возвращаемое значение имеет тип IEnumerable или IEnumerator. В теле блока итератора могут встречаться две конструкции: · yieldreturn формирует значение, выдаваемое на очередной итерации; · yieldbreak сигнализирует о завершении итерации. Ключевое слово yield имеет специальное значение для компилятора только в этих конструкциях. Код блока итератора выполняется не так, как обычные блоки. Компилятор формирует служебный объект-перечислитель, при вызове метода MoveNext которого выполняется код блока итератора, выдающий очередное значение с помощью ключевого слова yield. Следующий вызов методаMoveNext объекта-перечислителя возобновляет выполнение блока итератора с момента, на котором он был приостановлен в предыдущий раз. Индивидуальные задания: В программах требуется описать базовый класс (возможно, абстрактный), в котором с помощью виртуальных или абстрактных методов и свойств задается інтерфейс для производных классов. Целью индивидуального задания является максимальное использование наследования, даже если для конкретной задачи оно не дает выигрыша в объеме программы. Во всех классах следует переопределить метод Equals, чтобы обеспечить сравнение значений, а не ссылок. Функция Main должна содержать массив из элементов базового класса, заполненный ссылками на производные классы. В этой функции должно демонстрироваться использование всех разработанных элементов классов. Вариант 1 Создать класс Point (точка). На его основе создать классы ColoredPoint и Line (линия). На основе класса Line создать классы ColoredLine и PolyLine (многоугольник). В классах описать следующие элементы: • конструкторы с параметрами и конструкторы по умолчанию; • свойства для установки и получения значений всех координат, а также для изменения цвета и получения текущего цвета; • для линий – методы изменения угла поворота линий относительно первуй точки; • для многоугольника – метод масштабирования. Вариант 2 Создать абстрактный класс Vehicle (транспортное средство). На его основе реализовать классы Plane (самолет), Саr (автомобиль) и Ship (корабль). Классы должны иметь возможность задавать и получать координаты и параметры средств передвижения (цена, скорость, год выпуска и т. п.) с помощью свойств. Для самолета должна быть определена высота, для самолета и корабля – количество пассажиров, для корабля – порт приписки. Динамические характеристики задать с помощью методов. Вариант 3 Описать базовый класс Строка. Обязательные поля класса: • поле для хранения символов строки; • значение типа word для хранения длины строки в байтах. Реализовать обязательные методы следующего назначения: • конструктор без параметров; • конструктор, принимающий в качестве параметра строковый литерал; • конструктор, принимающий в качестве параметра символ; • метод получения длины строки; • метод очистки строки (сделать строку пустой). Описать производный от Строка класс Комплексное_число. Строки данного класса состоят из двух полей, разделенных символом i. Первое поле задает значение действительной части числа, второе – значение мнимой. Каждое из полей может содержать только символы десятичных цифр и символы - и +, задающие знак числа. Символы - или + могут находиться только в первой позиции числа, причем символ + может отсутствовать, в этом случае число считается положительным. Если в составе инициализирующей строки будут встречены любые символы, отличные от допустимых, класс Комплексное_число принимает нулевое значение. Примеры строк: 33 i 12 -7 И 00 +5 i -21 Для класса Комплексное_число определить следующие методы: • проверка на равенство; • сложение чисел; • умножение чисел. Вариант 4 Описать базовый класс Строка в соответствии с вариантом 3. Описать производный от Строка класс Десятичная_строка. Строки данного класса могут содержать только символы десятичных цифр и символы - и +, задающие знак числа. Символы - или + могут находиться только в первой позиции числа, причем символ + может отсутствовать, в этом случае число считается положительным. Если в составе инициализирующей строки будут встречены любые символы, отличные от допустимых, класс Десятичная_строка принимает нулевое значение. Содержимое данных строк рассматривается как десятичное число. Для класса определить следующие методы: • конструктор, принимающий в качестве параметра число; • арифметическая разность строк; • проверка на больше (по значению); • проверка на меньше (по значению). Вариант 5 Описать базовый класс Строка в соответствии с вариантом 3. Описать производный от Строка класс Битовая_строка. Строки данного класса могут содержать только символы '0' или '1'. Если в составе инициализирующей строки будут встречены любые символы, отличные от допустимых, класс Битовая_строка принимает нулевое значение. Содержимое данных строк рассматривается как двоичное число. Отрицательные числа хранятся в дополнительном коде. Для класса Битовая_строка определить следующие методы: • конструктор, принимающий в качестве параметра строковый литерал; • деструктор; • изменение знака на противоположный (перевод числа в дополнительный код); • присваивание; • вычисление арифметической суммы строк; • проверка на равенство. В случае необходимости более короткая битовая строка расширяется влево знаковым разрядом. Вариант 6 1. Описать базовый класс Элемент. Закрытые поля: · имя элемента (строка символов); · количество входов элемента; · количество выходов элемента. Методы: · конструктор класса без параметров; · конструктор, задающий имя и устанавливающий равным 1 количество входов и выходов; · конструктор, задающий значения всех полей элемента. Свойства: · имя элемента (только чтение); · количество входов элемента; · количество выходов элемента. 2. На основе класса Элемент описать производный класс Комбинационный, представляющий собой комбинационный элемент (двоичный вентиль), который может иметь несколько входов и один выход. Поле – массив значений входов. Методы: · конструкторы; · метод, задающий значение на входах экземпляра класса; · метод, позволяющий опрашивать состояние отдельного входа екземпляра класса; · метод, вычисляющий значение выхода (по варианту задания). 3. На основе класса Элемент описать производный класс Память, представляющий собой триггер. Триггер имеет входы, соответствующие типу тригера (см. далее вариант задания), и входы установки и сброса. Все триггеры считаются синхронными, сам синхровход в состав триггера не включается. Поля: · массив значений входов объекта класса, в массиве учитываются все входы (управляющие и информационные); · состояние на прямом выходе триггера; · состояние на инверсном выходе триггера. Методы: · конструктор (по умолчанию сбрасывает экземпляр класса); · конструктор копирования; · метод, задающий значение на входах экземпляра класса; · методы, позволяющие опрашивать состояния отдельного входа екземпляра класса; · метод, вычисляющий состояние экземпляра класса (по варианту задания) в зависимости от текущего состояния и значений на входах; · метод, переопределяющий операцию == для экземпляров класса. 4. Создать класс Регистр, используя класс Память как вложенный класс. Поля: · состояние входа ≪Сброс≫ – один для экземпляра класса; · состояние входа ≪Установка≫ – один для экземпляра класса; · массив типа Память заданной в варианте размерности; · массив (массивы), содержащий значения на соответствующих входах элементов массива типа Память. Методы: · метод, задающий значение на входах экземпляра класса; · метод, позволяющий опрашивать состояние отдельного выхода екземпляра класса; · метод, вычисляющий значение нового состояния экземпляра класса. Все поля классов Элемент, Комбинационный и Память должны быть описаны с ключевым словом private. В задании перечислены только обязательные члены и методы класса. Можно задавать дополнительные члены и методы, если они не отменяют обязательные и обеспечивают дополнительные удобства при работе с данными классами, например, описать функции вычисления выхода/состояния как виртуальные. 5. Для проверки функционирования созданных классов написать программу, использующую эти классы. В программе должны быть продемонстрированы все свойства созданных классов. Конкретный тип комбинационного элемента, тип триггера и разрядность регистра выбираются в соответствии с вариантом задания: Вариант | Комбинационный элемент | Число входов | Триггер | Разрядность регистра | | И-НЕ | | RS | | | ИЛИ | | RST | | | МОД2-НЕ | | D | | | И | | Т | | | ИЛИ-НЕ | | V | | | И | | RS | | | ИЛИ-НЕ | | JK | | | МОД2 | | D | | | И | | Т | | | ИЛИ | | JK | | | И-НЕ | | RS | | | ИЛИ-НЕ | | RST | | | МОД2 | | D | | | МОД2-НЕ | | Т | | | ИЛИ-НЕ | | V | | | И | | JK | | | И-НЕ | | RS | | | ИЛИ | | Т | | | МОД2 | | JK | | | МОД2-НЕ | | V | | |