Курс лекций для студентов специальности i-31 03 04 Информатика всех форм обучения Минск 2010



страница6/29
Дата09.08.2019
Размер0.64 Mb.
#126834
ТипКурс лекций
1   2   3   4   5   6   7   8   9   ...   29

2.7. ПЕРЕЧИСЛИТЕЛИ И ИТЕРАТОРЫ


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

int[] a = {1, 2, 4, 8};

foreach (var i in a)

{

Console.WriteLine(i);



}

Данный код работоспособен по двум причинам. Во-первых, любой массив относится к перечисляемым типам. Во-вторых, оператор foreach знает, как действовать с объектами перечисляемых типов. Перечисляемый тип (enumerable type) – это тип, который имеет экземплярный метод GetEnumerator(), возвращающий перечислитель. Перечислитель (enumerator) – объект, обладающий свойством Current, представляющим текущий элемент набора, и методом MoveNext() для перемещения к следующему элементу. Оператор foreach получает перечислитель, вызывая метод GetEnumerator(), а затем использует MoveNext() и Current для итерации по набору.

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

public class Shop

{

private string[] _items = new string[0];


public int ItemsCount

{

get { return _items.Length; }



}
public void AddItem(string item)

{

Array.Resize(ref _items, ItemsCount + 1);



_items[ItemsCount - 1] = item;

}
public string GetItem(int index)

{

return _items[index];



}

}

Пусть требуется сделать класс Shop перечисляемым. Для этого существует несколько способов:



    • Реализовать интерфейсы IEnumerable и IEnumerator.

    • Реализовать универсальные интерфейсы IEnumerable и IEnumerator.

    • Способ, при котором стандартные интерфейсы не применяются.

Интерфейсы IEnumerable и IEnumerator описаны в пространстве имён System.Collections:

public interface IEnumerable

{

IEnumerator GetEnumerator();



}
public interface IEnumerator

{

object Current { get; }



bool MoveNext();

void Reset();

}

Свойство для чтения Current представляет текущий объект. Для обеспечения универсальности это свойство имеет тип object. Метод MoveNext() выполняет перемещение на следующую позицию в наборе. Метод возвращает значение true, если дальнейшее перемещение возможно. Предполагается, что MoveNext() нужно вызвать и для получения первого элемента, то есть начальная позиция – «перед первым элементом». Метод Reset() сбрасывает позицию в начальное состояние.



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

public class Shop : IEnumerable

{

// опущены элементы ItemsCount, AddItem(), GetItem()


private class ShopEnumerator : IEnumerator

{

private readonly string[] _data; // локальная копия данных



private int _position = -1; // текущая позиция в наборе
public ShopEnumerator(string[] values)

{

_data = new string[values.Length];



Array.Copy(values, _data, values.Length);

}
public object Current

{

get { return _data[_position]; }



}
public bool MoveNext()

{

if (_position < _data.Length - 1)



{

_position++;

return true;

}

return false;



}
public void Reset()

{

_position = -1;



}

}
public IEnumerator GetEnumerator()

{

return new ShopEnumerator(_items);



}

}

Теперь класс Shop можно использовать следующим образом:



var shop = new Shop();

shop.AddItem("computer");

shop.AddItem("monitor");

foreach (string s in shop)

{

Console.WriteLine(s);



}

При записи цикла foreach объявляется переменная, тип которой совпадает с типом коллекции. Так как свойство IEnumerator.Current имеет тип object, то на каждой итерации выполняется приведение этого свойства к типу переменной цикла1. Это может повлечь ошибки времени выполнения. Избежать ошибок помогает реализация перечисляемого типа при помощи универсальных интерфейсов IEnumerable и IEnumerator:

public interface IEnumerable : IEnumerable

{

IEnumerator GetEnumerator();



}
public interface IEnumerator : IDisposable, IEnumerator

{

T Current { get; }



}

Универсальные интерфейсы IEnumerable и IEnumerator наследуются от обычных версий. У интерфейса IEnumerator типизированное свойство Current. Тип, реализующий интерфейс IEnumerable, должен содержать две версии метода GetEnumerator(). Обычно для IEnumerable.GetEnumerator() применяется явная реализация.

public class Shop : IEnumerable

{

// опущены элементы ItemsCount, AddItem(), GetItem()


private class ShopEnumerator : IEnumerator

{

private readonly string[] _data;



private int _position = -1;
public ShopEnumerator(string[] values)

{

_data = new string[values.Length];



Array.Copy(values, _data, values.Length);

}
public string Current

{

get { return _data[_position]; }



}
object IEnumerator.Current

{

get { return _data[_position]; }



}
public bool MoveNext()

{

if (_position < _data.Length - 1)



{

_position++;

return true;

}

return false;



}
public void Reset()

{

_position = -1;



}
public void Dispose() { }

}
public IEnumerator GetEnumerator()

{

return new ShopEnumerator(_items);



}
IEnumerator IEnumerable.GetEnumerator()

{

return GetEnumerator();



}

}

Возможна (хотя и нетипична) реализация перечисляемого типа без использования стандартных интерфейсов:



public class Shop

{

// опущены элементы ItemsCount, AddItem(), GetItem()


public ShopEnumerator GetEnumerator()

{

return new ShopEnumerator(_items);



}

}
public class ShopEnumerator

{

// реализация соответствует первому примеру,



// где ShopEnumerator – вложенный класс

// метод Reset() не реализован (это не часть перечислителя)

public string Current { get { . . . } }
public bool MoveNext() { . . . }

}

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



public class Shop : IEnumerable

{

// опущены элементы ItemsCount, AddItem(), GetItem()



public IEnumerator GetEnumerator()

{

return _items.GetEnumerator(); // перечислитель массива



}

}

Создать перечислить можно при помощи итератора. Итератор (iterator) – это операторный блок, который порождает упорядоченную последовательность значений. Итератор отличает присутствие одного или нескольких операторов yield. Оператор yield return <выражение> возвращает следующее значение последовательности, а оператор yield break прекращает генерацию последовательности. Итераторы могут использоваться в качестве тела метода, если тип метода – один из интерфейсов IEnumerator, IEnumerator, IEnumerable, IEnumerable.



Реализуем метод Shop.GetEnumerator() при помощи итератора.

public class Shop : IEnumerable

{

// опущены элементы ItemsCount, AddItem(), GetItem()


public IEnumerator GetEnumerator()

{

foreach (var s in _items)



{

yield return s;

}

}

}



Как видим, код заметно упростился. Элементы коллекции перебираются в цикле, и для каждого вызывается yield return s. Но достоинства итераторов этим не ограничиваются. В следующем примере в класс Shop добавляется метод, позволяющий перебрать коллекцию в обратном порядке.

public class Shop : IEnumerable

{

// опущены элементы, описанные ранее



public IEnumerable GetItemsReversed()

{

for (var i = _items.Length; i > 0; i--)



{

yield return _items[i - 1];

}

}

}


// пример использования

foreach (var s in shop.GetItemsReversed())

Console.WriteLine(s);

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

Поясним концепцию отложенных вычислений на примере. Пусть имеется класс Helper с итератором GetNumbers().

public static class Helper

{

public static IEnumerable GetNumbers()



{

int i = 0;

while (true) yield return i++;

}

}



Кажется, что вызов GetNumbers() приведет к «зацикливанию» программы. Однако использование итераторов обеспечивает этому методу следующее поведение. При первом вызове метод GetNumbers() вернёт значение i = 0, и состояние метода (значение переменной i) будет зафиксировано. При следующем вызове метод вернёт значение i = 1 и снова зафиксирует состояние, и так далее. Таким образом, следующий код успешно выводит на экран три числа:

foreach (var n in Helper.GetNumbers())

{

Console.WriteLine(n);



if (n == 2) break;

}

Рассмотрим ещё один пример итераторов. Пусть описан класс Range:



public class Range

{

public int Low { get; set; }



public int High { get; set; }
public IEnumerable GetNumbers()

{

for (int counter = Low; counter <= High; counter++)



{

yield return counter;

}

}

}



Будем использовать класс Range следующим образом:

var range = new Range {Low = 0, High = 10};

var enumerator = range.GetNumbers();

foreach (int number in enumerator)

{

Console.WriteLine(number);



}

На консоль будут выведены числа от 0 до 10. Интересно, что если изменить объект range после получения перечислителя enumerator, это повлияет на выполнение цикла foreach. Следующий код выводит числа от 0 до 5.

var range = new Range { Low = 0, High = 10 };

var enumerator = range.GetNumbers();

range.High = 5; // изменяем свойство объекта range

foreach (int number in enumerator)

{

Console.WriteLine(number);



}

Возможности итераторов широко используются в технологии LINQ, которая будет описана далее.



Каталог: images
images -> В списке студентов (или магистрантов)
images -> Н. И. Сулейманов Комплект контрольно-оценочных средств для оценки результатов освоения профессионального модуля разработан на основе Федерального государственного образовательного стандарта среднего профессионального
images -> По направлению подготовки
images -> Добавить гаджеты. Добавление гаджетов
images -> Техническое задание № apnip/C. 2/CS/Ind/01 Международный консультант по улучшенной производительности орошаемого земледелия
images -> Комплект контрольно-оценочных средств по профессиональному модулю пм. 01 Техническое обслуживание и ремонт автотранспорта


Поделитесь с Вашими друзьями:
1   2   3   4   5   6   7   8   9   ...   29




База данных защищена авторским правом ©vossta.ru 2022
обратиться к администрации

    Главная страница