1. Вступ
Уявімо: ви влаштовуєтеся на роботу до секретної лабораторії з вивчення котиків. Вам дають завдання — створити електронний словник котиків, де за кличкою можна швидко дізнатися вік, колір і розмір хвоста. Готове рішення вже є — це Dictionary<string, Cat>. Проте згодом шеф каже, що для деяких рідкісних порід потрібно, щоб словник зберігав порядок додавання, а інколи ще й щоб видалення виконувалося за особливою логікою.
А що, як вам потрібно написати метод, який прийматиме будь-який «котячий словник»? Неважливо, який саме клас — головне, щоб він підтримував базові операції «ключ-значення». Ось тут і з’являється інтерфейс IDictionary<TKey, TValue>.
IDictionary<TKey, TValue> — це загальний контракт, що визначає, яким є словник у .NET. Це не конкретний клас, а інтерфейс, тобто набір правил, яких має дотримуватися будь-який повноцінний словник:
- Швидкий пошук за ключем
- Додавання, видалення пар «ключ-значення»
- Перебирання всіх пар
- Перевірка наявності ключа
Якби Java існувала у Середньовіччі, її лицарі замість мечів проти драконів розмахували б ключами й значеннями. А C#-розробники просто реалізували б IDictionary<TKey, TValue> — і дракон (тобто ваш код) був би переможений.
2. Таблиця інтерфейсу IDictionary
Погляньмо на основні «обіцянки» (члени) цього інтерфейсу:
| Член інтерфейсу | Тип | Опис |
|---|---|---|
|
Властивість | Індексатор. Дозволяє отримувати або встановлювати значення, пов’язане з указаним ключем. Під час читання: якщо ключ не знайдено, викидає KeyNotFoundException. Під час запису: якщо ключ не знайдено, додає нову пару; якщо ключ існує, оновлює значення. Важливо: це найчастіший спосіб роботи зі словниками. |
|
Властивість | Повертає колекцію, що містить усі ключі у словнику. Дозволяє перебирати лише ключі. |
|
Властивість | Повертає колекцію, що містить усі значення у словнику. Дозволяє перебирати лише значення. |
|
Метод | Додає указаний ключ і значення до словника. Якщо ключ уже існує, викидає виняток ArgumentException. |
|
Метод | Визначає, чи містить словник елемент із указаним ключем. Повертає true, якщо ключ знайдено; інакше — false. Дуже корисно, щоб уникати помилок під час звернення до неіснуючого ключа через індексатор. |
|
Метод | Видаляє зі словника елемент із указаним ключем. Повертає true, якщо елемент успішно знайдено і видалено; інакше — false (наприклад, якщо ключ не знайдено). |
|
Метод | Отримує значення, пов’язане з указаним ключем. Це безпечний спосіб отримати значення, якщо ви не впевнені, чи існує ключ. Повертає true, якщо ключ знайдено, і value містить відповідне значення; інакше — false, а value міститиме значення за замовчуванням для TValue. Цей метод не генерує винятків, тож його часто використовують на практиці. |
3. Основні члени інтерфейсу IDictionary<TKey, TValue>
А тепер — до найважливішого! Погляньмо, що всередині інтерфейсу IDictionary<TKey, TValue>, і навчімося користуватися цим «контрактом» у вашому коді.
public interface IDictionary<TKey, TValue> : ICollection<KeyValuePair<TKey, TValue>>, IEnumerable<KeyValuePair<TKey, TValue>>
{
TValue this[TKey key] { get; set; } // Індексатор для доступу за ключем
ICollection<TKey> Keys { get; }
ICollection<TValue> Values { get; }
void Add(TKey key, TValue value); // Додати нову пару (ключ не має існувати)
bool ContainsKey(TKey key); // Чи існує такий ключ?
bool Remove(TKey key); // Видалити за ключем
bool TryGetValue(TKey key, out TValue value); // Безпечно отримати значення
}
Розберімо основні моменти:
Індексатор [key]
Дозволяє отримувати й задавати значення за ключем:
dictionary["Барсик"] = new Cat("Барсик", 2);
Властивості Keys і Values
Дозволяють отримати колекцію всіх ключів або всіх значень у словнику.
foreach (var name in dictionary.Keys)
{
Console.WriteLine(name);
}
Методи Add, Remove, ContainsKey, TryGetValue
- Add(key, value) — додати нову пару
- Remove(key) — видалити за ключем
- ContainsKey(key) — перевірити наявність ключа
- TryGetValue(key, out value) — безпечно отримати значення (без винятку, якщо ключа немає)
4. Використання IDictionary<TKey, TValue> на практиці
Універсальні методи
Уявіть, ви пишете метод, який має працювати зі словником, але не хоче прив’язуватися до конкретного класу: звичайний Dictionary, SortedDictionary чи, раптом, хтось зробив свій власний «криптоколективний словник».
Оголосіть параметр типу IDictionary<TKey, TValue>:
static void PrintDictionary<TKey, TValue>(IDictionary<TKey, TValue> someDictionary)
{
foreach (var pair in someDictionary)
{
Console.WriteLine($"{pair.Key}: {pair.Value}");
}
}
Тепер ваш метод приймає будь-який словник! Під капотом може бути що завгодно — навіть словник, який шифрує значення, якщо вам так до вподоби.
Використання IDictionary для передавання параметрів
Уявіть, ви створюєте застосунок для обліку котиків і хочете, щоб ваш метод працював із будь-якими словниками налаштувань, а не тільки з одним конкретним класом. Ось як це виглядає:
void SetCatParameters(IDictionary<string, string> parameters)
{
if (parameters.ContainsKey("color"))
{
Console.WriteLine($"Пофарбуйте котика у колір: {parameters["color"]}");
}
}
Єдина сигнатура — кілька реалізацій
Пояснімо на наочній таблиці:
| Клас колекції | Реалізує IDictionary<TKey,TValue> | Особливості |
|---|---|---|
|
✅ Так | Швидкий пошук; ключі в довільному порядку |
|
✅ Так | Ключі автоматично сортуються |
|
✅ Так | Ключі сортуються, економніше щодо памʼяті |
| (Свій клас, що реалізує інтерфейс) | ✅ Так | Будь-яка ваша логіка, але потрібно дотримуватися контракту |
5. Зв’язок з іншими інтерфейсами колекцій
Для справжніх ґіків: інтерфейс IDictionary<TKey, TValue> наслідує інтерфейси ICollection<KeyValuePair<TKey, TValue>> і IEnumerable<KeyValuePair<TKey, TValue>>. Це означає, що будь-який словник можна:
- Перебирати через foreach парами ключ-значення,
- Додавати та видаляти пари за допомогою методів колекції,
- Отримувати кількість елементів.
foreach (var entry in myDictionary)
{
Console.WriteLine($"{entry.Key} => {entry.Value}");
}
6. Особливості та типові помилки
Робота з індексатором
Найчастіша помилка новачків: спроба отримати елемент за відсутнім ключем спричинить виняток KeyNotFoundException.
var value = myDictionary["НемаТакогоКлюча"]; // Бум!
Тому завжди краще використовувати TryGetValue:
if (myDictionary.TryGetValue("Мурзик", out var cat))
{
Console.WriteLine($"Знайшли котика: {cat}");
}
else
{
Console.WriteLine($"Котика не знайдено!");
}
Додавання вже наявного ключа
Якщо викликати Add для ключа, який уже існує, буде викинуто ArgumentException. Якщо потрібно «додати або оновити», скористайтеся індексатором:
// Додає, якщо немає; оновлює, якщо є
myDictionary["Мурка"] = new Cat("Мурка", 5);
Перебирання й модифікація
Не намагайтеся модифікувати словник (наприклад, видаляти елементи) у циклі foreach безпосередньо: отримаєте виняток. Якщо треба щось видалити, спочатку зберіть список ключів для видалення, а потім видаляйте їх в окремому циклі:
// Безпечний спосіб видалення елементів
var keysToRemove = new List<string>();
foreach (var pair in myDictionary)
{
if (pair.Value.Age > 10) // Умова для видалення
{
keysToRemove.Add(pair.Key);
}
}
foreach (var key in keysToRemove)
{
myDictionary.Remove(key);
}
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ