Урок 5.3 — Словарь (Dictionary)
Пары ключ-значение для быстрого доступа к данным по уникальному ключу.
Представь себе телефонную книгу. Ты не ищешь номер по порядковому номеру записи — ты ищешь по имени контакта. Имя — это ключ, номер телефона — значение. Dictionary<TKey, TValue> в C# работает точно так же: ты передаёшь ключ и мгновенно получаешь значение. Внутри словарь использует хэш-таблицу, что обеспечивает очень быстрый доступ — O(1) в среднем случае.
Создание словаря
Словарь требует указать два типа: TKey (тип ключа) и TValue (тип значения). Ключи должны быть уникальными — ты не можешь добавить два элемента с одинаковым ключом.
using System.Collections.Generic;
// Пустой словарь (ключ — string, значение — int)
Dictionary<string, int> ages = new Dictionary<string, int>();
// Словарь с начальной ёмкостью
Dictionary<string, string> capitals = new Dictionary<string, string>(50);
// Инициализация через коллекцию пар
Dictionary<string, double> products = new Dictionary<string, double>
{
{ "молоко", 89.9 },
{ "хлеб", 45.0 },
{ "сыр", 299.5 }
};
// Инициализация через индекс (C# 6+)
Dictionary<int, string> employees = new Dictionary<int, string>
{
[101] = "Анна Смирнова",
[102] = "Борис Иванов",
[103] = "Вика Петрова"
};
// Target-typed new (C# 9+)
Dictionary<string, bool> settings = new();
Что можно использовать в качестве ключа? Любой тип, который реализует GetHashCode() и Equals(). Встроенные типы (int, string, double, DateTime и т.д.) работают из коробки. Для своих классов нужно переопределять GetHashCode и Equals — это мы разберём в уроках про ООП.
Доступ по ключу: dict[key]
Получить значение по ключу можно через квадратные скобки, как при индексации массива, только вместо индекса — ключ:
Dictionary<string, int> ages = new Dictionary<string, int>
{
{ "Анна", 25 },
{ "Борис", 32 },
{ "Вика", 28 }
};
int borisAge = ages["Борис"];
Console.WriteLine(borisAge); // 32
// ❌ Если ключа нет — KeyNotFoundException!
// int unknown = ages["Ольга"]; // Ошибка!
⚠️ KeyNotFoundException — ещё одна частая ошибка. Если обратиться к словарю по ключу, которого нет, программа упадёт. Всегда проверяй наличие ключа через ContainsKey() или используй TryGetValue().
Через индексатор можно также добавлять и обновлять элементы:
// Добавление через индексатор (если ключа нет)
ages["Ольга"] = 30; // добавится новая пара
// Обновление через индексатор (если ключ есть)
ages["Анна"] = 26; // обновится значение для ключа "Анна"
// Индексатор работает и для добавления, и для обновления:
// - Если ключа нет — добавляет
// - Если ключ есть — перезаписывает
Безопасный доступ: TryGetValue()
TryGetValue() — это безопасный способ получить значение. Он не выбрасывает исключение, а возвращает bool — найден ключ или нет:
Dictionary<string, int> ages = new()
{
{ "Анна", 25 },
{ "Борис", 32 }
};
if (ages.TryGetValue("Борис", out int borisAge))
{
Console.WriteLine($"Возраст Бориса: {borisAge}");
}
else
{
Console.WriteLine("Борис не найден");
}
if (ages.TryGetValue("Ольга", out int olgaAge))
{
Console.WriteLine($"Возраст Ольги: {olgaAge}");
}
else
{
Console.WriteLine("Ольга не найдена"); // это выполнится
}
// Можно объявить out-переменную прямо в условии
if (ages.TryGetValue("Анна", out int age))
{
Console.WriteLine($"Возраст: {age}");
}
Это гораздо лучше, чем ловить KeyNotFoundException! TryGetValue — идиоматический способ работы со словарями в C#.
Add, Remove, ContainsKey
Add() — добавить с проверкой
В отличие от индексатора, Add() выбрасывает ArgumentException, если ключ уже существует:
Dictionary<string, int> ages = new();
ages.Add("Анна", 25); // OK
ages.Add("Борис", 32); // OK
// ages.Add("Анна", 30); // ❌ ArgumentException! Ключ уже есть
// Лучше сначала проверить:
if (!ages.ContainsKey("Анна"))
{
ages.Add("Анна", 30);
}
else
{
ages["Анна"] = 30; // обновить существующее
}
Remove() — удалить по ключу
Удаляет пару по ключу. Возвращает true, если ключ был найден и удалён:
bool removed = ages.Remove("Борис"); // true — удалён
bool removedAgain = ages.Remove("Борис"); // false — уже нет
// Remove возвращает bool, можно использовать в условиях
if (ages.Remove("Анна"))
{
Console.WriteLine("Анна удалена из словаря");
}
ContainsKey() и ContainsValue()
Проверяют наличие ключа или значения:
bool hasAnna = ages.ContainsKey("Анна"); // true — по ключу
bool hasAge25 = ages.ContainsValue(25); // true — по значению (медленно!)
bool hasValue99 = ages.ContainsValue(99); // false
ContainsKey() — O(1) (быстро). ContainsValue() — O(n) (медленно, перебирает все значения). Используй ContainsValue только для маленьких словарей.
Перебор словаря
У словаря нет индекса, но его можно перебирать разными способами:
Перебор пар KeyValuePair
Самый частый способ — через foreach по KeyValuePair<TKey, TValue>:
Dictionary<string, int> ages = new()
{
{ "Анна", 25 },
{ "Борис", 32 },
{ "Вика", 28 }
};
foreach (KeyValuePair<string, int> kvp in ages)
{
Console.WriteLine($"{kvp.Key}: {kvp.Value} лет");
}
// Вывод:
// Анна: 25 лет
// Борис: 32 лет
// Вика: 28 лет
// Более краткая запись с var:
foreach (var kvp in ages)
{
Console.WriteLine($"{kvp.Key}: {kvp.Value}");
}
// Ещё короче — деконструкция (C# 7+):
foreach (var (name, age) in ages)
{
Console.WriteLine($"{name}: {age}");
}
Перебор ключей и значений отдельно
Свойства Keys и Values возвращают коллекции ключей и значений:
// Только ключи
Console.WriteLine("Имена:");
foreach (string name in ages.Keys)
{
Console.WriteLine($" {name}");
}
// Только значения
Console.WriteLine("Возраст:");
foreach (int age in ages.Values)
{
Console.WriteLine($" {age}");
}
// KeyValuePair — это структура (value type)
// Свойства Key и Value доступны только для чтения
Вложенные словари
Значением словаря может быть другой словарь. Это мощный приём для моделирования сложных структур данных:
// Словарь: отдел → (сотрудник → зарплата)
Dictionary<string, Dictionary<string, double>> company = new()
{
["IT"] = new()
{
{ "Анна", 150000 },
{ "Борис", 180000 },
{ "Вика", 160000 }
},
["HR"] = new()
{
{ "Глеб", 95000 },
{ "Дарья", 110000 }
},
["Sales"] = new()
{
{ "Егор", 85000 },
{ "Жанна", 92000 }
}
};
// Доступ к элементу
double annasSalary = company["IT"]["Анна"]; // 150000
// Проверка — безопасный доступ
if (company.TryGetValue("IT", out var itDept) &&
itDept.TryGetValue("Борис", out double borisSalary))
{
Console.WriteLine($"Зарплата Бориса: {borisSalary}");
}
// Перебор
foreach (var (department, employees) in company)
{
Console.WriteLine($"Отдел: {department}");
foreach (var (name, salary) in employees)
{
Console.WriteLine($" {name}: {salary} руб.");
}
}
Ещё пример — каталог товаров по категориям:
Dictionary<string, Dictionary<string, double>> catalog = new()
{
["Электроника"] = new()
{
["Наушники"] = 2990,
["Клавиатура"] = 1890,
["Мышь"] = 990
},
["Продукты"] = new()
{
["Молоко"] = 89.9,
["Хлеб"] = 45,
["Яйца (10 шт)"] = 120
}
};
// Достать цену наушников:
double headphonePrice = catalog["Электроника"]["Наушники"];
Другие полезные методы
Большой пример — подсчёт слов
Классическая задача: дан текст, нужно подсчитать, сколько раз встречается каждое слово. Словарь идеально подходит для этого:
using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
string text = "привет мир привет C# мир привет C# программирование";
string[] words = text.Split(' ');
Dictionary<string, int> wordCount = new Dictionary<string, int>();
foreach (string word in words)
{
if (wordCount.ContainsKey(word))
{
wordCount[word]++; // увеличиваем счётчик
}
else
{
wordCount[word] = 1; // первое упоминание
}
}
// Вывод результатов
foreach (var (word, count) in wordCount)
{
Console.WriteLine($"Слово '{word}' встречается {count} раз(а)");
}
// Поиск самого частого слова
string mostFrequent = "";
int maxCount = 0;
foreach (var (word, count) in wordCount)
{
if (count > maxCount)
{
mostFrequent = word;
maxCount = count;
}
}
Console.WriteLine($"\nСамое частое слово: '{mostFrequent}' ({maxCount} раз)");
}
}
// Вывод:
// Слово 'привет' встречается 3 раз(а)
// Слово 'мир' встречается 2 раз(а)
// Слово 'C#' встречается 2 раз(а)
// Слово 'программирование' встречается 1 раз(а)
// Самое частое слово: 'привет' (3 раз)
Что важно запомнить
dict[key] = value — добавляет или обновляет.foreach (var kvp in dict) или foreach (var (key, value) in dict). Свойства Keys и Values для отдельных коллекций.Dictionary<string, Dictionary<string, int>>. Удобно для каталогов и иерархических данных.Тест: 5.3: Словарь (Dictionary<TKey, TValue>)
3 вопроса