русс | укр

Мови програмуванняВідео уроки php mysqlПаскальСіАсемблерJavaMatlabPhpHtmlJavaScriptCSSC#DelphiТурбо Пролог

Компьютерные сетиСистемное программное обеспечениеИнформационные технологииПрограммирование


Linux Unix Алгоритмічні мови Архітектура мікроконтролерів Введення в розробку розподілених інформаційних систем Дискретна математика Інформаційне обслуговування користувачів Інформація та моделювання в управлінні виробництвом Комп'ютерна графіка Лекції


Перебір об'єктів (інтерфейс IEnumerable) і ітератори


Дата додавання: 2014-10-02; переглядів: 1006.


Оператор foreach є зручним засобом перебору елементів об'єкту. Масиви і всі стандартні колекції бібліотеки .NET дозволяють виконувати такий перебір завдяки тому, що в них реалізовані інтерфейси IEnumerable і IEnumerator. Для застосування оператора foreach до призначеного для користувача типу даних потрібно реалізувати в нім ці інтерфейси. Давайте подивимося, як це робиться.

Інтерфейс IEnumerable (що перераховує) визначає всього один метод - GetEnuraerator, що повертає об'єкт типу IEnumerator (нумератор), який можна використовувати для проглядання елементів об'єкту.

Інтерфейс IEnumerator задає три елементи:

· властивість Current, що повертає поточний елемент об'єкту;

· метод Movenext, що просуває нумератор на наступний елемент об'єкту;

· метод Reset, що встановлює нумератор в початок перегляду.

Цикл foreach використовує ці методи для перебору елементів, з яких складається об'єкт.

Таким чином, якщо потрібно, щоб для перебору елементів класу міг застосовуватися цикл foreach, необхідно реалізувати чотири методи: GetEnumerator, Current, MoveNext і Reset. Наприклад, якщо внутрішні елементи класу організовані в масив, потрібно буде описати закрите поле класу, що зберігає поточний індекс в масиві, в методі Movenext задати зміну цього індексу на 1 з перевіркою виходу за межу масиву, в методі Current - повернення елементу масиву по поточному індексу і так далі.

Це не цікава робота, а виконувати її доводиться часто, тому і були введені засоби, що полегшують виконання перебору в об'єкті, - ітератори. Ітератором є блок коду, задаючий послідовність перебору елементів об'єкту. На кожному проході циклу foreach виконується один крок ітератора, що закінчується видачею чергового значення. Видача значення виконується за допомогою ключового слова yield.

Розглянемо створення ітератора на прикладі лістингу 9.6. Нехай потрібно створити об'єкт, що містить бойову групу екземплярів типу Monster. Для простоти обмежимо максимальну кількість бійців в групі десятьма.

 

Лістинг 9.6. Клас з ітератором

using System;

using System.Collections;

namespace examp59

{

class Monster

{

string name;

int health;

int ammo;

 

public Monster( int health, int ammo, string name )

{

this. health = health;

this.ammo = ammo;

this.name = name;

}

 

public Monster()

{

name = "ЛЮДА";

ammo = 100;

health = 100;

}

 

public Monster(string name)

{

this.name = name;

ammo = 200;

health = 200;

}

 

public void Passport()

{

Console.WriteLine(" {0} {1} {2} ", name, ammo, health);

 

}

}

 

class Daemon : Monster

{

public int brain;

public Daemon(): base()

{

brain = 1;

}

}

 

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 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 Program

{

 

static void Main(string[] args)

{

 

Stado s = new Stado();

s.Add( new Monster());

s.Add( new Monster("Bacя"));

s.Add( new Monster("Петя"));

foreach ( Monster i in s ) i.Passport();

foreach ( Monster i in s.Backwards()) i.Passport();

foreach ( Monster i in s.MonstersOnly()) i.Passport();

}

}

}

 

Все, що потрібно зробити для підтримки перебору, - вказати, що клас реалізує інтерфейс IEnumerable (оператор 1), і описати ітератор (оператор 2). Доступ до нього може бути здійснений через методи MoveNext і Current інтерфейсу IEnumerator.

За кодом, приведеним в лістингу 9.6, стоїть велика внутрішня робота компілятора. На кожному кроці циклу foreach для ітератора створюється “оболонка” - службовий об'єкт, який запам'ятовує поточний стан ітератора і виконує все необхідне для доступу до елементів об'єкту, що проглядаються. Іншими словами, код, що становить ітератор, не виконується так, як він виглядає - у вигляді безперервної послідовності, а розбитий на окремі ітерації, між якими стан ітератора зберігається.

У лістингу 9.7 приведений приклад ітератора, що перебирає чотири задані рядки.

 

Лістинг 9.7. Простий ітератор

using System;

using System.Collections;

namespace examp60

{

class Num : IEnumerable

{

public IEnumerator GetEnumerator()

{

yield return "one";

yield return "two";

yield return "three";

yield return "oops";

}

}

 

class Program

{

static void Main(string[] args)

{

foreach ( string s in new Num() ) Console.WriteLine( s );

}

}

}

 

Результат роботи програми:

one

two

three

oops

 

Наступний приклад демонструє перебір значень в заданому діапазоні (від 1 до 5):

 

using System;

using System.Collections;

using System.Linq;

using System.Text;

 

namespace examp61

{

class Program

{

 

public static IEnumerable Count( int from, int to )

{

from = 1;

while ( from <= to ) yield return from++;

}

 

static void Main(string[] args)

{

foreach (int i in Count(1, 5)) Console.WriteLine(i);

}

}

}

 

Перевага використання ітераторів полягає в тому, що для одного і того ж класу можна задати різний порядок перебору елементів. У лістингу 9.8 описано дві додаткові стратегії перебору елементів класу Stado, введеного в лістингу 9.6, - перебір в зворотному порядку і вибірка тільки тих об'єктів, які є екземплярами класу Monster (для цього використаний метод отримання типу об'єкту GetType, успадкований від базового класу object).

Лістинг 9.8. Реалізація декількох стратегій перебору

using System;

using System.Collections;

 

namespace examp62

{

class Monster

{

string name;

int health;

int ammo;

public Monster(int health, int ammo, string name)

{

this.health = health;

this.ammo = ammo;

this.name = name;

}

public Monster()

{

name = "ЛЮДА";

ammo = 100;

health = 100;

}

public Monster(string name)

{

this.name = name;

ammo = 200;

health = 200;

}

public void Passport()

{

Console.WriteLine(" {0} {1} {2} \n", name, ammo, health);

 

}

}

class Daemon : Monster

{

public int brain;

public Daemon()

: base()

{

brain = 1;

}

}

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 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 Program

{

static void Main(string[] args)

{

Stado s = new Stado();

s.Add( new Monster());

s.Add( new Monster("Bacя"));

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.

У тілі блоку ітератора можуть зустрічатися дві конструкції:

· yield return формує значення, що видається на черговій ітерації;

· yield break сигналізує про завершення ітерації.

Ключове слово yield має спеціальне значення для компілятора тільки в цих конструкціях.

Код блоку ітератора виконується не так, як звичайні блоки. Компілятор формує службовий об'єкт-нумератор, при виклику методу MoveNext якого виконується код блоку ітератора, що видає чергове значення за допомогою ключового слова yield. Наступний виклик методу MoveNext об'єкту-нумератора відновлює виконання блоку ітератора з моменту, на якому він був припинений в попередній раз.

 


<== попередня лекція | наступна лекція ==>
Клонування об'єктів (інтерфейс IСloneable) | Структури


Онлайн система числення Калькулятор онлайн звичайний Науковий калькулятор онлайн