Студопедия

КАТЕГОРИИ:


Архитектура-(3434)Астрономия-(809)Биология-(7483)Биотехнологии-(1457)Военное дело-(14632)Высокие технологии-(1363)География-(913)Геология-(1438)Государство-(451)Демография-(1065)Дом-(47672)Журналистика и СМИ-(912)Изобретательство-(14524)Иностранные языки-(4268)Информатика-(17799)Искусство-(1338)История-(13644)Компьютеры-(11121)Косметика-(55)Кулинария-(373)Культура-(8427)Лингвистика-(374)Литература-(1642)Маркетинг-(23702)Математика-(16968)Машиностроение-(1700)Медицина-(12668)Менеджмент-(24684)Механика-(15423)Науковедение-(506)Образование-(11852)Охрана труда-(3308)Педагогика-(5571)Полиграфия-(1312)Политика-(7869)Право-(5454)Приборостроение-(1369)Программирование-(2801)Производство-(97182)Промышленность-(8706)Психология-(18388)Религия-(3217)Связь-(10668)Сельское хозяйство-(299)Социология-(6455)Спорт-(42831)Строительство-(4793)Торговля-(5050)Транспорт-(2929)Туризм-(1568)Физика-(3942)Философия-(17015)Финансы-(26596)Химия-(22929)Экология-(12095)Экономика-(9961)Электроника-(8441)Электротехника-(4623)Энергетика-(12629)Юриспруденция-(1492)Ядерная техника-(1748)

Клас object 2 страница




 

Лістинг 9.3. Сортування по двом критеріям

using System;

using System.Collections;

using System.Linq;

using System.Text;

 

namespace examp56

{

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 int Ammo

{

get { return ammo; }

set

{

if (value > 0) ammo = value; else ammo = 0;

}

}

public string Name

{

get { return name; }

}

virtual public void Passport()

{

Console.WriteLine("Monster {0} \t health - {1} ammo - {2}",

name, health, ammo);

}

 

public class SortByName: IComparer

{

int IComparer.Compare(object ob1, object ob2)

{

Monster m1 = (Monster)ob1;

Monster m2 = (Monster)ob2;

return String.Compare(m1.Name, m2.Name);

}

}

public class SortByAmmo: IComparer

{

int IComparer.Compare(object ob1, object ob2)

{

Monster m1 = (Monster)ob1;

Monster m2 = (Monster)ob2;

if (m1.Ammo > m2.Ammo) return 1;

if (m1.Ammo < m2.Ammo) return -1;

return 0;

}

}

}

 

class Program

{

static void Main(string[] args)

{

const int n = 3;

Monster[] stado = new Monster[n];

stado[0] = new Monster(50, 50, "Вася");

stado[1] = new Monster(80, 80, "Петя");

stado[2] = new Monster(40, 10, "Маша");

Console.WriteLine("Сортировка по имени:");

Array.Sort(stado, new Monster.SortByName());

foreach (Monster elem in stado) elem.Passport();

Console.WriteLine("Сортировка по вооружению:");

Array.Sort(stado, new Monster.SortByAmmo());

foreach (Monster elem in stado) elem.Passport();

}

}

}

 

 

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

Сортування по імені:

Monster Вася health = 50 ammo 50

Monster Маша health = 40 ammo 10

Monster Петя health = 80 ammo 30

 

Сортування по озброєнню:

Monster Маша health = 40 ammo 10

Monster Вася health = 50 ammo 50

Monster Петя health = 80 ammo 80

 

9.5.3 Перевантаження операцій відношення

Якщо клас реалізує інтерфейс IСomparable, його екземпляри можна порівнювати між собою за принципом більше або менше. Логічно дозволити використовувати для цього операції відношення, які перенавантажують їх. Операції повинні перевантажуватися парами: < і >, <= і >= == і! =. Перевантаження операцій зазвичай виконується шляхом делегування, тобто звернення до перевизначених методів Соmpare і Equals.

Якщо клас реалізує інтерфейс IСomparable, потрібно перевизначити метод Equals і пов'язаний з ним метод GetHashcode. Обидва методи успадковано від базового класу object.

У лістингу 9.4 операцій відношення перевантажені для класу Monster. Як критерій порівняння об'єктів за принципом більше або менше виступає поле health, а при порівнянні на рівність реалізується значуща семантика, тобто попарно порівнюються всі поля об'єктів

 

Лістинг 9.4. Перевантаження операцій відношення

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

 

namespace examp57

{

class Monster: IComparable

{

string name;

int health;

int ammo;

 

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

{

this. health = health;

this.ammo = ammo;

this.name = name;

}

public override bool Equals(object obj)

{

if (obj == null || GetType()!= obj.GetType()) return false;

Monster temp = (Monster) obj;

return health == temp.health

&& ammo == temp.ammo

&& name == temp.name;

}

public override int GetHashCode()

{

return name.GetHashCode();

}

public static bool operator ==(Monster a, Monster b)

{

return a.Equals(b);

}

// варіант оператора:

// public static bool operator == (Monster a. Monster b)

// {

// return (a.CompareTo(b) == 0):

// }

 

 

public static bool operator!= (Monster a, Monster b)

{

return!a.Equals(b);

}

// варіант:

// public static bool operator!= (Monster a, Monster b)

// {

// return (a.CompareTo(b)!= 0);

// }

 

public static bool operator < (Monster a, Monster b)

{return (a.CompareTo(b) < 0);}

 

public static bool operator > (Monster a, Monster b)

{return (a.CompareTo(b) > 0);}

 

public static bool operator <= (Monster a, Monster b)

{return (a.CompareTo(b) <= 0);}

 

public static bool operator >= (Monster a, Monster b)

{return (a.CompareTo(b) >= 0);}

 

public int CompareTo(object obj)

{

Monster temp = (Monster) obj; if (this.health > temp.health) return 1;

if (this.health < temp.health) return -1;

return 0;

}

 

}

 

class Program

{

static void Main(string[] args)

{

Monster Вася = new Monster(70, 80, "Вася");

Monster Петя = new Monster(80, 80, "Петя");

if (Вася > Петя) Console.WriteLine ("Вася більше Петі");

else if (Вася == Петя) Console.WriteLine("Вася = Петя");

else Console.WriteLine("Вася менше Петі");

 

}

}

}

 

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

Вася менше Петі

 

9.5.4. Клонування об'єктів (інтерфейс IСloneable)

Клонування - це створення копії об'єкту. Копія об'єкту називається клоном. Як вам відомо, при привласненні одного об'єкту посилального типу іншому копіюється посилання, а не сам об'єкт (рис. 9.1, а). Якщо необхідно скопіювати в іншу область пам'яті поля об'єкту, можна скористатися методом MemberwiseСlone, який будь-який об'єкт успадковує від класу object. При цьому об'єкти, на які указують поля об'єкту, що у свою чергу є посиланнями, не копіюються (рис. 9.1, б). Це називається поверхневим клонуванням.

 

 

Рис. 9.1. Клонування об'єктів

 

Для створення повністю незалежних об'єктів необхідне глибоке клонування, коли в пам'яті створюється дублікат всього дерева об'єктів, тобто об'єктів, на які посилаються поля об'єкту, поля полів і так далі (рис. 9.1, в ). Алгоритм глибокого клонування дуже складний, оскільки вимагає рекурсивного обходу всіх посилань об'єкту і відстежування циклічних залежностей.

Об'єкт, що має власні алгоритми клонування, повинен оголошуватися як спадкоємець інтерфейсу ICloneable і перевизначати його єдиний метод Clone. У лістингу 9.4 приведений приклад створення поверхневої копії об'єкту класу Monster за допомогою методу MemberwiseClone, а також реалізований інтерфейс ICloneable. У демонстраційних цілях в ім'я клона об'єкту додано слово “Клон”.

Метод MemberwiseClone можна викликати тільки з методів класу. Він не може бути викликаний безпосередньо, оскільки оголошений в класі object як захищений (protected).

 

Лістинг 9.5. Клонування об'єктів

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

namespace examp58

{

class Monster: ICloneable

{

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 ShallowClone() // поверхнева копія

{ return (Monster)this.MemberwiseClone(); }

public object Clone() // призначена копія для користувача

{

return new Monster(this.health, this.ammo, "Клон " + this.name);

}

virtual public void Passport()

{

Console.WriteLine("Monster {0} \t health = {1} ammo = {2}",

name, health, ammo);

}

}

class Program

{

static void Main(string[] args)

{

Monster Вася = new Monster(70, 80, "Вася");

Monster X = Вася;

X.Passport();

Monster Y = Вася.ShallowClone();

Y.Passport();

Monster Z = (Monster)Вася.Clone();

Z.Passport();

}

}

}

 

Об'єкт X посилається на ту ж область пам'яті, що і об'єкт Вася. Отже, якщо ми внесемо зміни до одного з цих об'єктів, це відіб'ється на іншому. Об'єкти Y і Z, створені шляхом клонування, володіють власними копіями значень полів і незалежні від початкового об'єкту.

 

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

Оператор 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 об'єкту-нумератора відновлює виконання блоку ітератора з моменту, на якому він був припинений в попередній раз.

 




Поделиться с друзьями:


Дата добавления: 2014-12-27; Просмотров: 384; Нарушение авторских прав?; Мы поможем в написании вашей работы!


Нам важно ваше мнение! Был ли полезен опубликованный материал? Да | Нет



studopedia.su - Студопедия (2013 - 2024) год. Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав! Последнее добавление




Генерация страницы за: 0.175 сек.