1. Вступ
Уявіть, що ви — власник невеличкого магазину і маєте список товарів. Кожен товар має свій унікальний артикул (наприклад, ART-001, ART-002) і, звісно, назву, ціну та кількість на складі.
Якби ви зберігали все це у List<T>, де T — це, скажімо, наш майбутній клас Product, то, щоб знайти товар з артикулом ART-005, довелося б перебирати весь список:
«Це ART-001? Ні. Це ART-002? Ні… А це ART-005! Знайшли!»
Якщо товарів 10 — не проблема. А якщо у вас 10 000 товарів? Або 100 000? Пошук кожного товару займатиме купу часу. Вашому покупцеві доведеться дуже довго чекати, поки ви знайдете його улюблену пачку печива. Непорядок!
Потрібен спосіб, що дасть змогу миттєво перейти до потрібного товару, якщо ми знаємо його унікальний артикул, не перебираючи всі інші. Тобто нам потрібен якийсь «ключ», який указує прямо на потрібне «значення».
Знайомство з Dictionary
І тут на сцену виходить Dictionary<TKey, TValue>! Уявіть, що це не просто список, а дуже розумна телефонна книга. У звичайній телефонній книзі ви шукаєте номер телефону (значення) за імʼям людини (ключем). Відкриваєте книгу на літеру «А», потім шукаєте «Олексій», і ось — його номер. Не потрібно гортати всі номери підряд, доки не знайдете потрібний.
Точно так само Dictionary (або «Словник» у перекладі з англійської) зберігає дані у вигляді пар «ключ-значення».
- Ключ (TKey): це унікальний ідентифікатор для кожного елемента. Він як імʼя в телефонній книзі або артикул товару. За цим ключем ви шукатимете потрібне значення. Ключ має бути унікальним у межах одного словника. Якщо ви спробуєте додати елемент з уже наявним ключем, Dictionary цього не дозволить.
- Значення (TValue): це самі дані, які ви хочете зберігати. Це може бути номер телефону, ціна товару, опис терміна — що завгодно!
Букви TKey і TValue у кутових дужках <TKey, TValue> означають, що Dictionary — це узагальнена (Generic) колекція. Ви самі вирішуєте, який тип даних буде у ключа і який — у значення. Це може бути string як ключ (імʼя, артикул), int (ID користувача) або навіть ваш власний клас. А значенням може бути int, string, double або, знову ж таки, повноцінний обʼєкт.
Перевага? Миттєвий доступ! Завдяки особливій внутрішній структурі (хеш-таблиці — якщо казати науково, але не переймайтеся, нині не обовʼязково знати, як вона влаштована) Dictionary дає змогу знайти значення за ключем за дуже короткий час, незалежно від того, скільки елементів у словнику — десять чи мільйон. Це як суперіндекс у великій бібліотеці: ви кажете «мені потрібна книга з C#», і вам миттєво показують, де вона лежить, не змушуючи гортати кожну полицю.
Почнімо одразу зі справи! Ми розвиватимемо наш проєкт, створюючи інтерактивний «Словничок термінів з C#», який допоможе нам самим запамʼятовувати нові поняття.
2. Основи синтаксису: створюємо словник
Усе починається традиційно: оголошуємо змінну, але тепер не забуваємо вказати не один, а два типи — тип ключа (TKey) і тип значення (TValue):
// Простий словник: ключ - string (логін), значення - string (email)
Dictionary<string, string> userEmails = new Dictionary<string, string>();
// Або коротше з var
var userEmails = new Dictionary<string, string>();
Чому обовʼязково вказувати обидва типи?
Адже C# — мова суворої типізації, і словник має «знати», які типи ключів і значень ви для нього підготували.
Додавання елементів
Щоб додати нову пару «ключ-значення», використовуємо метод Add. Ключ має бути унікальним!
userEmails.Add("vasya", "vasya@example.com");
userEmails.Add("petya", "petya@gmail.com");
Якщо спробувати додати ще раз із тим самим ключем — словник відхилить операцію і викине виняток.
Доступ до значень за ключем
Найзручніше у словнику — отримання значення за ключем:
string email = userEmails["vasya"];
Console.WriteLine(email); // vasya@example.com
Якщо звернутися за неіснуючим ключем, програма одразу викине виняток KeyNotFoundException. Щоб працювати безпечно, далі розглянемо методи перевірки наявності ключа.
Зміна значення за ключем
Якщо такий ключ уже є, просто присвоюємо нове значення:
userEmails["vasya"] = "vasya@newmail.com"; // тепер email користувача vasya змінено
Якщо ключа не було — таке присвоєння створить новий елемент у словнику.
Приклад з користувачами
Удосконалимо наш навчальний застосунок. Ми зберігали список завдань (List<string> tasks;) для ToDo-застосунку. Припустімо, тепер хочемо додати «авторизацію»: кожному користувачеві потрібна електронна адреса.
Ось як це може виглядати:
// UserId — це string, Email теж string
var users = new Dictionary<string, string>();
users.Add("admin", "admin@myapp.com");
users.Add("alice", "alice@wonderland.com");
users.Add("bob", "bob@builder.com");
Тепер ви завжди можете швидко дізнатися email будь-якого користувача за його логіном:
Console.WriteLine(users["alice"]); // => alice@wonderland.com
3. Основні методи та властивості Dictionary
| Метод/Властивість | Опис |
|---|---|
|
Додає нову пару «ключ-значення». |
|
Видаляє елемент за ключем. |
|
Перевіряє, чи є такий ключ. |
|
Перевіряє, чи є таке значення (повільно!). |
|
Безпечно отримати значення за ключем, не кидаючи виняток. |
|
Кількість пар «ключ-значення» у словнику. |
|
Колекція всіх ключів. |
|
Колекція всіх значень. |
Перевірка наявності ключа
Найпоширеніше й безпечне — спочатку перевіряти, чи є ключ:
if (users.ContainsKey("dasha"))
{
Console.WriteLine(users["dasha"]);
}
else
{
Console.WriteLine("Користувача dasha не знайдено!");
}
Безпечний спосіб: TryGetValue
Метод TryGetValue дає змогу уникнути винятку:
if (users.TryGetValue("bob", out string email))
{
Console.WriteLine($"Електронна адреса Bob: {email}");
}
else
{
Console.WriteLine("Bob не знайдено!");
}
Це добра практика і про неї прямо питають на співбесідах — варто запамʼятати одразу. До того ж цей метод працює швидше, ніж звʼязка ContainsKey + звернення за індексом.
4. Перебирання словника: цикл foreach
Якщо потрібно пройтися всіма парами, використовуйте цикл foreach. Кожен елемент словника — це обʼєкт типу KeyValuePair<TKey, TValue>:
foreach (var pair in users)
{
Console.WriteLine($"Логін: {pair.Key}, Email: {pair.Value}");
}
Або, якщо бажаєте лаконічніше:
foreach (var (login, email) in users)
{
Console.WriteLine($"{login}: {email}");
}
// Такий синтаксис став доступний завдяки деконструкції кортежів (C# 7+).
5. Видалення та зміна значень
Видалити користувача за логіном просто:
users.Remove("alice");
Якщо такого ключа немає — метод поверне false. Можна сміливо викликати видалення, не боячись винятку.
Змінити електронну адресу користувача:
users["bob"] = "bob@constructor.com";
Якщо такого ключа не було — зʼявиться нова пара!
6. Службові властивості Keys і Values
Якщо потрібен лише список логінів (ключів) або лише електронних адрес (значень), використовуйте колекції Keys і Values:
foreach (string login in users.Keys)
{
Console.WriteLine("Логін: " + login);
}
foreach (string email in users.Values)
{
Console.WriteLine("Електронна адреса: " + email);
}
7. Важливі нюанси словника
Ключі мають бути унікальні
Тобто не можна додати два однакові ключі. Якщо спробуєте — отримаєте виняток. Така унікальність забезпечує цілісність даних: в одного користувача не може бути двох електронних адрес (по одному запису).
Ключ не може бути null (для string)
Для рядкових ключів спроба додати ключ null спричинить помилку (ArgumentNullException). Якщо у вас раптом немає ключа, подумайте — можливо, це ознака проблеми з логікою даних.
Чому пошук у словнику такий швидкий?
В основі Dictionary — хеш-таблиця. Це означає, що пошук за ключем — не перебирання всіх елементів підряд, а блискавичне обчислення спеціальної «хеш-функції» і майже прямий доступ до комірки, де лежить значення.
Що можна використовувати як ключ?
- Будь-який тип, для якого коректно реалізовано порівняння на рівність і отримання унікального коду (методи Equals і GetHashCode()).
- Зазвичай це string, int, Guid або ваші власні типи (але тоді варто бути обережними з перевизначенням Equals/GetHashCode, інакше можете отримати непередбачувані помилки).
8. Додаємо словник у застосунок
У нашому мінізастосунку ToDo додамо словник користувачів і реалізуємо функцію пошуку електронної адреси за логіном з обробкою помилок:
using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
// Словник користувачів: логін => email
var users = new Dictionary<string, string>
{
{ "admin", "admin@myapp.com" },
{ "alice", "alice@wonderland.com" },
{ "bob", "bob@builder.com" }
};
Console.WriteLine("Введіть логін користувача для пошуку email:");
string login = Console.ReadLine();
// Безпечний пошук електронної адреси
if (users.TryGetValue(login, out string email))
{
Console.WriteLine($"Електронна адреса користувача {login}: {email}");
}
else
{
Console.WriteLine($"Користувача {login} не знайдено.");
}
// Перебирання всіх користувачів
Console.WriteLine("\nСписок усіх користувачів:");
foreach (var pair in users)
{
Console.WriteLine($"{pair.Key} => {pair.Value}");
}
}
}
9. Типові помилки та пастки новачків
Іноді дуже хочеться зробити ось так:
// У надії, що якщо ключа немає — усе буде гаразд
string value = users["nonexistent"]; // Отримаємо: KeyNotFoundException!
Не забувайте: завжди перевіряйте наявність ключа (ContainsKey або TryGetValue), якщо не впевнені, що він точно є.
Памʼятайте також: перебирання значень не гарантує їхню унікальність! Один і той самий email може бути у двох логінів (якщо раптом втрачено контроль над унікальністю значень, а не ключів).
Часто плутають і методи — наприклад, намагаються видалити за значенням:
users.Remove("bob@builder.com"); // Не видалить! Очікується ключ, а не значення.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ