Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
веб.doc
Скачиваний:
0
Добавлен:
01.07.2025
Размер:
933.89 Кб
Скачать

1 Колекції

 

1.1   Інтерфейси та типи колекцій

Більшість класів колекцій знаходяться в просторі імен System.Collections і System.Collections.Generic. Класи узагальнених колекцій можна знайти в просторі імен System.Collections.Generic. Класи колекцій, що спеціалізовані для зберігання певного типу, знаходяться в просторі імен SystemCollections.Specialized. Класи колекцій, безпечних щодо потоків, визначені в просторі імен System.Collections.Concurrent.

Звичайно, існують і інші способи поділу на групи класів колекцій. На основі інтерфейсів, реалізованих класами колекцій, колекції можуть бути згруповані в списки, власне колекції та словники.

У таблиці 1.1 описані інтерфейси, реалізовані колекціями та списками, а також їх методи і властивості.

 

Таблиця 1.1. Інтерфейси, їх методи і властивості, важливі для колекцій

Інтерфейс

Опис

IEnumerable<T>

Інтерфейс IEnumerable необхідний, коли з колекцією використовується оператор foreach. Цей інтерфейс визначає метод GetEnumerator(), який повертає перераховувач, що реалізує IEnumerator.

ІCollection<T>

ІCollection<T> - це інтерфейс, реалізований класами узагальнених колекцій. З його допомогою можна отримати кількість елементів в колекції (властивість Count) і скопіювати колекцію в масив (метод СоруТо()). Можна також додавати і видаляти елементи з колекції (Add(), Remove(), Clear()).

ІList<T>

Інтерфейс ІList <T> призначений для створення списків, елементи яких доступні по своїх позиціях. Цей інтерфейс визначає індексатор, а також способи вставки і видалення елементів в певні позиції (методи Insert() і Remove()). IList<T> наслідуваний від ICollection<T>.

ISet<T>

Інтерфейс ІSet<T> з'явився у версії. NET 4. Цей інтерфейс реалізується множинами. Він дозволяє комбінувати різні множини в об'єднання, а також перевіряти, чи не перетинаються чи дві множини. ISet<T> унаслідуваний від ICollection<T>.

IDictionary<TKey, TValue>

Інтерфейс IDictionary<TKey, TValue> реалізується узагальненими класами колекцій, елементи яких складаються з ключа і значення. За допомогою цього інтерфейсу можна отримувати доступ до всіх ключів і значень, витягувати елементи по індексатор типу ключа, а також додавати і видаляти елементи.

ILookup<TKey, TValue>

Подібно IDictionary<TKey, TValue> підтримує ключі і значення. Однак у цьому випадку колекція може містити множинні значення для одного ключа.

IComparer<T>

Інтерфейс IComparer<T> реалізований компаратором і

використовується для сортування елементів усередині

колекції за допомогою методу Compare().

 

1.2   Списки

Для динамічних списків в .NET Framework передбачений узагальнений клас List<T>. Цей клас реалізує інтерфейсиIList, ICollection, IEnumerable, IList<T>, ICollection<T> і IEnumerable<T>.

 

1.2.1 Створення списків

Створювати спискові об'єкти можна, викликаючи конструктор за замовчуванням. При оголошенні узагальненого класу List<T> необхідно вказувати тип збережених значень. У наведеному нижче коді показано, як оголошувати спискиList<T> з елементами int і Racer. Клас ArrayList - це неузагальнених список, що приймає елементи будь-якого типу, похідного від Object.

Конструктор за замовчанням створює порожній список. Як тільки елементи починають додаватися до списку, його ємність збільшується до 4 елементів. При додаванні п'ятого елемента розмір списку змінюється так, щоб умістити 8 елементів. Якщо ж і цього недостатньо, список знову розширюється, на цей раз до 16 елементів. При кожному розширенні ємність списку подвоюється.

 

var intList = new List<int>();

var racers = new List<Racer>();

 

При зміні ємності списку вся колекція цілком переміщається в новий блок пам'яті. У реалізації List<T>використовується масив типу Т. При переміщенні створюється новий масив, і за допомогою Array.Copy() проводиться копіювання елементів старого масиву в новий. Щоб заощадити час, коли кількість елементів, що підлягають розміщенню в списку, відомо заздалегідь, ємність можна визначити в конструкторі. Нижче створюється колекція ємністю в 10 елементів. Якщо цієї ємності буде недостатньо для розміщення всіх елементів, то вона подвоюється - спочатку до 20, потім до 40 елементів.

 

List<int> intList = new List<int>(10);

 

За допомогою властивості Capacity можна отримувати і встановлювати ємність колекції:

 

intList.Capacity = 20;

 

Ємність колекції – це не те ж саме, що кількість елементів в колекції. Кількість елементів в колекції може бути прочитане у властивості Count. Зрозуміло, ємність завжди більше або дорівнює кількості елементів. До тих пір, поки жоден елемент не доданий в колекцію, кількість дорівнює 0.

 

Console.WriteLine (intList.Count);

 

Якщо ви завершили додавання елементів в колекцію і не збираєтеся додавати нові, то можете позбутися зайвої ємності, викликавши метод TrimExcess(). Однак оскільки реорганізація колекції вимагає часу, TrimExcess() не робить нічого, якщо кількість елементів перевищує 90% від поточної ємності.

 

intList.TrimExcess();

1.2.2 Ініціалізатори колекцій

Присвоювати значення колекціям можна за допомогою ініціалізаторів колекцій. Синтаксис ініціалізаторів колекцій подібний ініціалізаторам масивів. У ініціалізаторі колекції значення присвоюються колекції всередині фігурних дужок при ініціалізації колекції:

 

var intList = new List <int> () {1, 2};

var stringList =

new List <string> () {"one", "two"};

 

1.2.3 Додавання елементів

Додавати елементи в список можна методом Add(), як показано нижче. Узагальнений параметричний тип визначає тип першого параметра методу Add().

 

var intList = new List<int>();

intList.Add(1);

intList.Add(2);

var stringList = new List<string>();

stringList.Add ("one") ;

stringList.Add("two");

 

1.2.4 Вставлення елементів

Для вставки елементів у певну позицію колекції служить метод Insert().

 

1.2.5 Доступ до елементів

Всі класи, що реалізують інтерфейси IList і IList<T>, надають індексатор, так що до елементів можна звертатися з використанням індексатора, передаючи йому номер елемента. Перший елемент доступний за індексом 0. Вказуючиracers[3], ви звернетеся до четвертого елементу списку:

 

Racer rl = racers [3] ;

 

Отримавши кількість елементів з властивості Count, ви можете виконати цикл for для проходу за всіма елементами колекції, застосовуючи індексатор для звернення до них:

 

for (int i=0; i < racers.Count; i++)

{

Console.WriteLine(racers[i]);

}

 

Оскільки List<T> реалізує інтерфейс IEnumerable, прохід по елементах колекції можна також здійснювати за допомогою оператора foreach:

 

foreach (Racer r in racers)

{

Console.WriteLine(r);

 

1.2.6 Видалення елементів

Елементи можна видаляти за індексом або передаючи елемент, який підлягає видаленню. Нижче видаляється четвертий по порядку елемент:

 

racers.RemoveAt(3);

 

Щоб видалити об'єкт Racer, його можна також безпосередньо передати методу Remove(). Видалення за індексом працює швидше, оскільки в цьому випадку не доводиться виконувати пошук елемента, який видаляється, по всій колекції. Метод Remove() спочатку шукає в колекції індекс елемента, що видаляється, за допомогою методу IndexOf(), а потім використовує цей індекс для видалення елемента. IndexOf() спочатку перевіряє, чи реалізує тип елемента інтерфейсIEquatable<T>. Якщо це так, викликається метод Equals() цього інтерфейсу для знаходження елемента в колекції, що збігається з переданим методу. Якщо ж цей інтерфейс не реалізований, для порівняння елементів застосовується методEquals() класу Object. Реалізація за замовчуванням методу Equals() класу Object виконує побітове порівняння типів значень, але для посилальних типів порівнює тільки посилання.

 

1.2.7 Пошук

Існують різні способи пошуку елементів у колекції. Можна отримати індекс знайденого елемента або сам знайдений елемент. Для використання доступні такі методи, як IndexOf(), LastlndexOf(), Findlndex(), FindLastlndex(), Find() і FindLast(). Для перевірки існування елементу клас List<T> пропонує метод Exists().

Метод IndexOf() в якості параметра очікує об'єкт і повертає індекс елемента, якщо такий знайдений в колекції. Якщо ж елемент не знайдений, повертається -1. Слід пам'ятати, що IndexOf() для порівняння елементів використовує інтерфейсIEquatable<T>.

 

int indexl = racers.IndexOf(mario);

 

У методі IndexOf() можна також вказати, що пошук не повинен проводитися по всій колекції, а замість цього задати індекс позиції, з якої слід починати пошук, і кількість елементів, які потрібно переглянути.

Замість пошуку певного елемента за допомогою методу IndexOf() можна шукати елемент, що володіє певними характеристиками, які мають визначатися в методі Findlndex(). Метод Findlndex() очікує параметра типу Predicate:

 

public int Findlndex(Predicate<T> match);

 

Тип Predicate<T> - це делегат, який повертає булеве значення і приймає тип Т в якості параметра. Якщо предикат повертає true, значить, виявлено відповідність, і елемент знайдений. Якщо ж повертається false, значить, елемент не знайдений і пошук триває.

 

public delegate bool Predicate<T> (T obj);

 

1.2.8 Сортування

Клас List<T> дозволяє сортувати свої елементи за допомогою методу Sort(), в якому реалізований алгоритм швидкого сортування.

Для використання доступні декілька перевантажень методу Sort(). Аргументи, які можуть йому передаватися – це делегат Comparison<T>, узагальнений інтерфейс IComparer<T> і діапазон разом з узагальненим інтерфейсомIComparer<T>:

 

public void List<T>.Sort () ;

public void List<T>.Sort(Comparison<T>);

public void List<T>.Sort(IComparer<T>);

public void List<T>.Sort(Int32, Int32, IComparer<T>);

 

Використовувати метод Sort() без аргументів можна тільки в тому випадку, коли елементи колекції реалізують інтерфейс IComparable.

Клас Racer реалізує інтерфейс IComparable<T> для сортування гонщиків по прізвищах:

 

racers.Sort ();

racers.ForEach(Console.WriteLine);

 

Якщо сортування повинна бути виконана іншим способом, а не таким, який підтримується за замовчуванням типом елементів, потрібно скористатися іншою технікою. Наприклад, передавати об'єкт, який реалізує інтерфейс IComparer<T>.

 

1.2.9 Колекції, доступні тільки для читання

Після того, як колекції створені, вони доступні для читання і запису. Звичайно, вони повинні бути такими, інакше ви не зможете наповнити їх значеннями. Тим не менше, після заповнення колекції є можливість створити колекцію, доступну тільки для читання. Колекція List<T> має метод AsReadOnly, який повертає об'єкт типу ReadOnlyCollection<T>. КласReadOnlyCollection<T> реалізує ті ж інтерфейси,що і List<T>, але всі методи і властивості, які змінюють колекцію, генерують виключення NotSupportedException.

 

1.3 Черга

Черга (queue) – це колекція, в якій елементи обробляються за схемою "перший увійшов, перший вийшов" (first in, first out - FIFO). Елемент, вставлений в чергу першим, першим же і читається. Прикладами черг можуть служити черги в аеропорту, черга претендентів на працевлаштування, черга друку принтера або циклічна черга потоків на виділення ресурсів процесора. Часто зустрічаються черги, в яких елементи обробляються по-різному, у відповідності з пріоритетом. Наприклад, в черзі в аеропорту пасажири бізнес-класу обслуговуються перед пасажирами економ-класу. Тут може використовуватися кілька черг – по одній для кожного пріоритету. В аеропорту це можна бачити наочно, оскільки там передбачені дві стійки реєстрації для пасажирів бізнес-класу та економ-класу. Те ж справедливо і для черг друку і диспетчера потоків. У вас може бути масив списку черг, де елемент масиву означає пріоритет. Усередині кожного елемента масиву буде черга, і обробка буде виконуватися за принципом FIFO.

Черга реалізується за допомогою класу Queue<T> з простору імен System. Collections.Generic. Усередині класQueue<T> використовує масив типу Т, подібно до того, як це робить клас List<T>. Він реалізує інтерфейси IEnumerable<T>і ICollection, але не ICollection<T>. Інтерфейс ICollection<T> не реалізований, оскільки він визначає методи Add() іRemove(), які не повинні бути доступні для черги.

Клас Queue<T> не реалізує інтерфейс IList<T>, тому звертатися до елементів черги через індексатор не можна. Чергу дозволяє лише додавати елементи, при цьому елемент міститься в кінець черги (методом Enqueue()), а також отримувати елементи з голови черги (методом Dequeue()).

На рисунку 1.1 показані елементи черги. Метод Enqueue() додає елементи в кінець черги, елементи читаються і видаляються на іншому кінці черги за допомогою методу Dequeue(). Кожен наступний виклик методу Dequeue() видаляє наступний елемент черги.

 

Прямокутник 13

Рисунок 1.1 – Проста черга

 

При створенні черг можна використовувати конструктори, подібні тим, що застосовувалися з типом List<T>. Конструктор за умовчанням створює порожню чергу, але конструктор можна також використовувати для вказівки початкової ємності. У міру додавання елементів в чергу ємність зростає, дозволяючи розмістити спочатку 4, потім 6, 16 і 32 елемента, якщо ємність не визначена. Подібно класу List<T>, ємність черги при необхідності подвоюється. Конструктор за замовчуванням неузагальнених класу Queue відрізняється тим, що створює початковий масив з 32 порожніх елементів. Використовуючи перевантажені конструктори, можна передавати будь-яку колекцію, яка реалізує інтерфейсIEnumerable<T>, вміст якої копіюється в чергу.

Методи класу Queue<T> описані в таблиці 1.2.

 

Таблиця 1.2. Члени класу Queue<T>

Вибрані члени класу Queue<T>

Опис

Count

Властивість Count повертає кількість елементів в черзі.

Enqueue()

Метод Enqueue() додає елемент в кінець черги.

Dequeue()

Метод Dequeue() читає і видаляє елемент з голови черги. Якщо на момент виклику методу Dequeue() елементів в черзі більше немає, тоді буде генеруватися виключення InvalidOperationException.

Peek()

Метод Peek() читає елемент з голови черги, але не видаляє його.

TrimExcess()

Метод TrimExcess() змінює ємність черги. Метод Dequeue() видаляє елемент із черги, але не змінює її ємності. TrimExcess() дозволяє позбутися від порожніх елементів на початку черги.

 

Прикладом програми, який демонструє використання класу Queue<T>, може бути програма управління документами.

 

1.4 Стек

Стек (stack) – це ще один контейнер, дуже схожий на чергу. Для доступу до елементів у ньому використовуються інші методи. Елемент, доданий до стеку останнім, читається першим. Стек – це контейнер, що працює за принципом "останній увійшов, перший вийшов" (last in, first out - LIFO).

На рисунку 1.2 показано представлення стека, де метод Push() додає елемент, а метод Pop() - отримує елемент, доданий останнім.

 

Прямокутник 12

Рисунок 1.2 – Простий стек

 

Подібно класу Queue<T>, клас Stack<T> реалізує інтерфейси IEnumerable<T> і ICollection.

Члени класу Stack<T> перераховані в таблиці 1.3.

 

Таблиця 1.3. Члени класу Stack<T>

Вибрані члени класуStack<T>

Опис

Count

Властивість Count повертає кількість елементів в стеку.

Push()

Метод Push() додає елемент в вершину стека.

Pop()

Метод Pop() видаляє і повертає елемент з вершини стека. Якщо стек порожній, генерується виключення типу InvalidOperationException.

Peek()

Метод Peek() читає елемент з вершини стека, але не видаляє його.

Contains ()

Метод Contains() перевіряє наявність елемента в стеку і повертає true в разі знаходження його там.

 

У наступному прикладі за допомогою методу Push() в стек поміщаються три елементи. Оператором для кожного виконується ітерація по всіх елементах з використанням інтерфейсу IEnumerable. Перечислитель стека не видаляє елементів з нього - він тільки повертає їх.

 

var alphabet = new Stack<char>();

alphabet.Push('A');

alphabet.Push('B');

alphabet.Push('С');

foreach (string item in alphabet)

{

Console.Write(item);

}

Console.WriteLine();

 

Оскільки елементи читаються в порядку від останнього доданого до першого, отримуємо наступний результат:

СВА

1.5 Словники

Словник (dictionary) являє собою складну структуру даних, що дозволяє забезпечити доступ до елементів за ключем. Головна властивість словників - швидкий пошук на основі ключів. Можна також вільно додавати і видаляти елементи, подібно тому, як це робиться в List<T>, але без накладних витрат продуктивності, пов'язаних з необхідністю зсуву наступних елементів в пам'яті.

На рисунку 1.3 представлена ​​спрощена модель словника. Тут ключами словника служать ідентифікатори співробітників, такі як В4711. Ключ трансформується в хеш. У хеші створюється число для асоціації індексу зі значенням. Після цього індекс містить посилання на значення. Зображена модель є спрощеною, оскільки існує можливість того, що єдине входження індексу може бути асоційоване з декількома значеннями, і індекс може зберігатися у вигляді дерева.

 

Прямокутник 11

Рисунок 1.3 – Спрощена модель словника

 

У .NET Framework пропонується декілька класів словників. Головний клас, який можна використовувати – цеDictionary<TKey, TValue>.

1.5.1 Тип ключа

Тип, використовуваний в якості ключа словника, повинен перевизначати метод GetHashCode() класу Object. Всякий раз, коли клас словника повинен знайти розташування елемента, він викликає метод GetHashCode().

Ціле число, що повертається цим методом, використовується словником для обчислення індексу, куди поміщений елемент. Ми не станемо заглиблюватися в подробиці роботи цього алгоритму. Єдине, що слід знати - це те, що він використовує прості числа, так що ємність словника завжди виражається простим числом.

Реалізація методу GetHashCode() повинна задовольняти перерахованим нижче вимогам:

-         Один і той самий об'єкт повинен завжди повертати одне і те ж значення.

-         Різні об'єкти можуть повертати одне і те ж значення.

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

-         Він не повинен генерувати винятків.

-         Він повинен використовувати як мінімум одне поле екземпляра.

-         Значення хеш-коду повинні розподілятися рівномірно по всьому діапазону чисел, які може зберігати int.

-         Хеш-код не повинен змінюватися протягом часу існування об'єкта.

Крім реалізації GetHashCode() тип ключа також має реалізовувати метод IEquatable<T>.Equals() або перевизначати метод Equals() класу Object. Оскільки різні об'єкти ключа можуть повертати один і той же хеш-код, метод Equals()використовується при порівнянні ключів словника. Словник перевіряє два ключі А і В на еквівалентність, викликаючиA.Equals(В). Це означає, що потрібно забезпечити істинність наступного твердження.

Якщо істинно A.Equals(В), значить, A.GetHashCode() і В.GetHashCode() завжди повинні повертати один і той же хеш-код.

Між іншим, System.String реалізує інтерфейс IEquatable і відповідно перевизначає GetHashCode(). Метод Equals()забезпечує порівняння значень, а GetHashCode() повертає хеш-код, заснований на значенні рядка. Стрічки цілком можуть використовуватися в якості ключів в словниках.

Числові типи, такі як Int32, також реалізують інтерфейс IEquatable і перевантажують GetHashCode(). Однак хеш-код, що повертається цими типами, просто відображається на значення. Якщо число, яке ви хочете використовувати в якості ключа, саме по собі не розподілено по всьому діапазону можливих цілочисельних значень, застосування цілих в якості ключів не відповідає правилу рівномірного розподілу ключових значень для досягнення найкращої продуктивності. ТипInt32 не призначений для застосування в словнику.

Якщо потрібно використовувати тип ключа, який не реалізує IEquatable і не перевизначає GetHashCode відповідно значенням ключа, що його записала в словнику, то можна створити компаратор, який реалізує інтерфейсIEqualityComparer<T>. Інтерфейс IEqualityComparer<T> визначає методи GetHashCode() і Equals() з аргументом - переданим об'єктом, так що можна надати реалізацію, відмінну від типу самого об'єкта. Перевантаження конструктораDictionary<TKey, TValue> дозволяє передати об'єкт, який реалізує IEqualityComparer<T>. Якщо такий об'єкт присвоєно словником, цей клас використовується для генерації хеш-кодів і порівняння ключів.

 

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]