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.ru"; // теперь email Vasy изменился
Если ключа не было — такое присваивание создаст новый элемент в словаре.
Пример с пользователями
Давайте немного доработаем наше учебное приложение. Мы хранли список задач (List<string> tasks;) для ToDo-программы. Допустим, теперь хотим добавить "авторизацию": каждому пользователю нужен email.
Вот как это может выглядеть:
// 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. Можно смело пытаться удалить, не боясь исключения.
Изменить email пользователя:
users["bob"] = "bob@constructor.com";
Если такого ключа не было — появится новая пара!
6. Служебные свойства Keys и Values
Если нужен только список логинов (ключей) или только email (значений), используйте коллекции Keys и Values:
foreach (string login in users.Keys)
{
Console.WriteLine("Логин: " + login);
}
foreach (string email in users.Values)
{
Console.WriteLine("Email: " + email);
}
7. Важные нюансы словаря
Ключи должны быть уникальны
То есть нельзя добавить два одинаковых ключа. Если попытаетесь — получите исключение. Такая уникальность обеспечивает безопасность данных: у одного пользователя не может быть сразу двух e-mail (по одной записи).
Ключ не может быть null (для string)
Для строк-ключей попытка добавить ключ null вызовет ошибку (ArgumentNullException). Если у вас вдруг нет ключа, подумайте — возможно, это признак проблемы с логикой данных.
Почему поиск в словаре такой быстрый?
Dictionary устроен "под капотом" на хеш-таблице. Это значит, что поиск по ключу — не перебор всех элементов подряд, а молниеносное вычисление специальной "хеш-функции" и практически прямой доступ к ячейке, где лежит значение.
Что можно использовать в качестве ключа?
- Любой тип, для которого корректно реализовано сравнение на равенство и получение уникального кода (методы Equals и GetHashCode()).
- Обычно это string, int, Guid или ваши собственные типы (но тогда нужно быть аккуратнее с переопределением Equals/GetHashCode, иначе можно словить веселые баги).
8. Добавляем словарь в приложение
В нашем мини-приложении ToDo мы добавим словарь пользователей, и реализуем функцию поиска email по логину с обработкой ошибок:
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();
// Безопасный поиск email
if (users.TryGetValue(login, out string email))
{
Console.WriteLine($"Email пользователя {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"); // Не удалит! Ожидается ключ, а не значение.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ