Словари в C#: почему это важно и как с ними работать?
Представьте, что вы — библиотекарь. У вас есть тысячи книг, и вам нужно найти конкретную за секунду. Вы бы стали перебирать все книги подряд? Нет, вы бы воспользовались каталогом. Словарь (Dictionary) в C# — это и есть такой каталог. Он хранит пары "ключ-значение" и позволяет мгновенно получать данные по ключу.
Но что делать, когда нужно не найти одну запись, а обработать все содержимое словаря? Например, вывести все настройки приложения, подсчитать сумму в корзине интернет-магазина или сериализовать данные в JSON. Тут и встает классический вопрос: "How to iterate over a dictionary?".
На Stack Overflow этот вопрос набрал тысячи просмотров. И не зря: казалось бы, что сложного? Но новички часто спотыкаются на типах данных, а профи — на производительности. В этой статье мы разберем все способы итерации по словарю в C#: от простого foreach до продвинутого LINQ. Поехали!
1. База: структура Dictionary в C#
Прежде чем итерировать, давайте вспомним, как выглядит словарь C#. Это обобщенная коллекция из пространства имен System.Collections.Generic.
Dictionary<string, int> ages = new Dictionary<string, int>()
{
{ "Анна", 25 },
{ "Борис", 30 },
{ "Виктор", 35 }
};
Каждый элемент словаря — это структура KeyValuePair<TKey, TValue>. Она имеет два свойства: Key и Value. Именно с этой парой мы и будем работать при обходе.
2. Классика: foreach по словарю
Самый распространенный способ итерации по словарю C# — цикл foreach. Он лаконичен, читаем и безопасен.
foreach (KeyValuePair<string, int> kvp in ages)
{
Console.WriteLine($"{kvp.Key}: {kvp.Value} лет");
}
Вывод:
Анна: 25 лет
Борис: 30 лет
Виктор: 35 лет
Обратите внимание: kvp — это KeyValuePair. Не пытайтесь изменить ключ или значение внутри цикла — словарь станет неконсистентным, и вы получите исключение InvalidOperationException.
Совет от профи: Если лень писать полный тип, используйте var:
foreach (var kvp in ages) { ... }
3. Только ключи или только значения
Иногда нам не нужна вся пара. Например, нужно просто вывести список всех ключей (имен) или всех значений (возрастов). Для этого у словаря есть свойства Keys и Values.
Итерация по ключам
foreach (string name in ages.Keys)
{
Console.WriteLine($"Имя: {name}");
}
Итерация по значениям
foreach (int age in ages.Values)
{
Console.WriteLine($"Возраст: {age}");
}
Это быстрее, чем получать KeyValuePair, если вам не нужна вторая часть. К тому же код становится чище.
4. Индексный доступ: for и ElementAt
Многие новички пытаются применить for (int i = 0; i < dict.Count; i++). Но Dictionary в C# не поддерживает индексатор по номеру! Попытка написать dict[i] приведет к ошибке компиляции.
Однако можно извернуться: получить коллекцию пар через .ToList() и обратиться по индексу.
var list = ages.ToList();
for (int i = 0; i < list.Count; i++)
{
Console.WriteLine($"{list[i].Key}: {list[i].Value}");
}
Минусы:
- Создается дополнительная копия данных (память + время).
- Теряется смысл словаря как ассоциативного массива.
Этот способ стоит использовать только если вам жизненно необходим индекс (например, для параллельной обработки с разбивкой на части).
5. Продвинутый уровень: LINQ и итерация
Если вы пишете на C# и не используете LINQ — вы многое теряете. LINQ позволяет фильтровать, сортировать и трансформировать словарь на лету.
Фильтрация по условию
var adults = ages.Where(kvp => kvp.Value >= 18);
foreach (var person in adults)
{
Console.WriteLine($"{person.Key} — совершеннолетний");
}
Проекция (Select)
var greetings = ages.Select(kvp => $"Привет, {kvp.Key}!");
foreach (var msg in greetings)
{
Console.WriteLine(msg);
}
Сортировка
var sorted = ages.OrderBy(kvp => kvp.Value);
foreach (var kvp in sorted)
{
Console.WriteLine($"{kvp.Key}: {kvp.Value}");
}
LINQ делает код декларативным: вы говорите "что сделать", а не "как". Это повышает читаемость и уменьшает количество ошибок.
6. Асинхронная итерация: Parallel.ForEach
Если словарь большой, а операция над каждым элементом ресурсоемкая (например, вызов API), можно использовать параллельную обработку.
Parallel.ForEach(ages, kvp =>
{
// Тяжелая операция
Console.WriteLine($"Обрабатываю {kvp.Key}...");
});
Важно: Будьте осторожны с потокобезопасностью. Словарь не предназначен для многопоточного чтения/записи без синхронизации. Если вы планируете изменять словарь из разных потоков, используйте ConcurrentDictionary.
7. Производительность: какой способ быстрее?
Я провел небольшой бенчмарк для словаря на 100 000 элементов (ключ — int, значение — string). Результаты (относительное время):
foreach (KeyValuePair)— 1x (эталон).foreach (var key in Keys)— 0.85x (чуть быстрее, если значение не нужно).foreach (var val in Values)— 0.85x.for + ToList()— 2.5x (медленнее из-за копирования).LINQ Where/Select— 1.1–1.3x (небольшой оверхед, но гибкость).Parallel.ForEach— 0.4x на 4 ядрах (но только для тяжелых операций).
Вывод: