Использование стандартного интерфейса IEnumerable
На первый взгляд преимуществ во введении интерфейсного класса нет – методы кодирования и декодирования можно разместить непосредственно в классе MyzikText.
Реально существующие в библиотеке платформы .NET классы включают большое число интерфейсных методов различных интерфейсов, наследуя которые, классы получают дополнительные свойства – через название методов, а не через их реализацию. Реализацию интерфейсных методов каждый интерфейсный класс, как правило, должен выполнять самостоятельно. Например, если в нашем классе необходимо организовать просмотр с помощью цикла foreach некоторых перечисляемых объектов представленных массивом, то наш класс должен быть наследником интерфейса IEnumerable(перечислимый). У этого интерфейса всего один метод GetEnumerator(), возвращающий объект типа Enumerator(перечислитель). Формат записи метода GetEnumerator() имеет следующий вид:
IEnumerator GetEnumerator();
Таким образом, наш класс должен быть наследником интерфейсов IEnumerable и IEnumerator.
Интерфейса IEnumerator включает одно свойство Object Current{get;}, возвращающее очередной перечисляемый объект, и два метода – bool MoveNext(), передвигающий перечислитель на следующий перечисляемый объект, и метод void Reset(), устанавливающий перечислитель на первый перечисляемый объект.
В совокупности именно указанное свойство и эти два метода позволяют организовать процесс просмотра объектов массивов с помощью цикла foreach. Методы этих интерфейсов работают с виртуальной коллекцией (набором объектов определяемых в процессе работы программы), что и определяет их универсальность.
Если в классе необходимо выполнять сравнение объектов, например, при их сортировке, то такой класс следует объявить наследником интерфейса IComparable. Этот интерфейс имеет всего один метод CompareTo(object obj), возвращающий целочисленное значение, положительное, отрицательное или равное нулю, в зависимости от выполнения отношения "больше", "меньше" или "равно".
Рассмотрим работу с интерфейсами IEnumerable и IEnumerator на учебном примере, в котором необходимо организовать просмотр товаров некоторого магазина. Для простоты считаем, что класс Tovar имеет два поля название товара и его цена.
class Tovar
{
public string Naz; // Название и цена товара
public int Cena;
public Tovar(string n, int c) // Конструкор товара
{
Naz = n;
Cena = c;
}
}
Для хранения объектов типа Tovarиспользуем класс Cklad, имеющий следующую структуру:
class Cklad
{
public Tovar[] tovar; // Массив товаров
public Cklad() // Конструктор склада
{
tovar = new Tovar[4];
}
}
Максимальное количество объектов, которое может храниться на складе, в учебных целях принято равным 4.
Рассмотрим работу программы без использования интерфейса IEnumerable.
Код программы:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public static string s;
public static int kol;
class Tovar
{
public string Naz; // Название и цена товара
public int Cena;
public Tovar(string n, int c) // Конструкор товара
{
Naz = n;
Cena = c;
}
}
class Cklad
{
public Tovar[] tovar; // Массив товаров
public Cklad() // Конструктор склада
{
tovar = new Tovar[4];
}
}
public Form1()
{
InitializeComponent();
kol = 0;
s = "";
}
Cklad ckl = new Cklad();
private void button2_Click(object sender, EventArgs e)
{
if (kol < 4)
{
ckl.tovar[kol] = new Tovar(textBox1.Text, Convert.ToInt32(textBox2.Text));
s = s + textBox1.Text + textBox2.Text + "\r\n";
}
else { s = s + "CKLAD POLHIJ" + "\r\n"; kol--; }
kol++;
textBox3.Text = s;
}
private void button1_Click(object sender, EventArgs e)
{
s = "";
s = "Работает цикл foreach"+ "\r\n";
foreach (Tovar t in ckl.tovar)
{
s = s + t.Naz + " " + t.Cena.ToString() + "\r\n";
}
s = s + "Работает цикл for" + "\r\n";
for (int i = 0; i < kol; i++)
{
s = s + ckl.tovar[i].Naz + " " + ckl.tovar[i].Cena.ToString() + "\r\n";
}
textBox3.Text = s;
}
}
}
Работа программы
Рисунок 11.2 – Работа программы без интерфейсов
Необходимо отметить, что в программе цикл foreach используется только для переменной типа массив ckl.tovar, который уже имеет встроенный интерфейс – все классы массивы, независимо от типа элементов, реализуют перечисление элементов массива, и для них определен метод GetEnumerator.
Однако, если мы попытаемся использовать цикл foreach для объектов класса Tovar в объекте ckl типа Cklad , а не для массива tovar объекта класса Cklad, например,
foreach (Tovar t in ckl)
{
s = s + t.Naz + " " + t.Cena.ToString() + "\r\n";
}
то программа выдаст сообщение об ошибке:
«foreach statement cannot operate on variables of type ' WindowsFormsApplication1.Form1.Cklad ' because 'Books' does not contain a public definition for 'GetEnumerator'»
(Оператор foreach не может применяться к переменным типа ' WindowsFormsApplication1.Form1.Cklad ' , так как переменные этого класса не содержат открытого определения метода 'GetEnumerator').
Доопределим нашу программу необходимым интерфейсом, для этого в нашей программе необходимо добавить дополнительное пространство имен:
using System.Collections;
Класс Cklad должен наследовать интерфейс IEnumerable:
class Cklad : IEnumerable
В тело класса Cklad необходимо включить реализацию метода GetEnumerator:
public IEnumerator GetEnumerator()
{
for (int i = 0; i < 4; i++) yield return tovar[i];
}
Необходимы некоторые комментарии, которые взяты из книги Т.А.Павловской (стр. 207.)
«Таким образом, если требуется, чтобы для перебора элементов класса мог применяться цикл foreach, необходимо реализовать четыре метода: GetEnumerator, Current, MoveNextи Reset. Например, если внутренние элементы класса организованы в массив, потребуется описать закрытое поле класса, хранящее текущий индекс в массиве, в методе MoveNext задавать изменение этого индекса на 1 с проверкой выхода за границу массива, в методе Current – возврат элемента массива по текущему индексу и т.д.
Это не интересная работа, а выполнять ее приходится часто, поэтому в версии 2.0 были введены средства, облегчающие выполнение перебора в объекте – итераторы.
Итератор представляет собой блок кода, задающий последовательность перебора элементов объекта. На каждом проходе цикла foreach выполняется один шаг итератора, заканчивающийся выдачей очередного значения. Выдача значения выполняется с помощью ключевого слова yield.
. . .
Все, что требуется сделать в версии 2.0 для поддержки перебора — указать, что класс реализует интерфейс IEnumerable, и описать итератор. Доступ к нему может быть осуществлен через методы MoveNext и Currentинтерфейса IEnumerator. За кодом, приведенным в листинге итератора, стоит большая внутренняя работа компилятора.
На каждом шаге цикла foreach для итератора создается «оболочка» — служебный объект, который запоминает текущее состояние итератора и выполняет все необходимое для доступа к просматриваемым элементам объекта. Иными словами, код, составляющий итератор, не выполняется так, как он выглядит — в виде непрерывной последовательности, а разбит на отдельные итерации, между которыми состояние итератора сохраняется.».
Приведенные два небольших изменения в программе позволяют использовать циклforeach, который ранее выдавал сообщение об ошибке.
Необходимо отметить особенность работы цикла foreach, которая может приводить к «зависанию» программ. Если в нашей программе, после ввода нескольких объектов товара, но не до полного заполнения массива, мы включим режим просмотра товаров на складе, то программа «повиснет» при попытке вывода несуществующих значений – необходимо контролировать «перебираемые» значения цикла foreach.
Цикл for не имеет этих недостатков, потому что его конечное значение определяется текущим значением глобальной переменной kol.
for (int i = 0; i < kol; i++)
{
s = s + ckl.tovar[i].Naz + " " + ckl.tovar[i].Cena.ToString() + "\r\n";
}