Avatar ·

Как правильно перебрать (итерировать) все элементы Dictionary в C#?

Всем привет! Я только начал изучать C# и столкнулся с проблемой при работе со словарями (Dictionary). У меня есть простой словарь, который хранит имена студентов и их оценки: ```csharp Dictionary students = new Dictionary { { "Анна", 85 }, { "Иван", 92 }, { "Мария", 78 } }; ``` Я хочу вывести на экран всех студентов и их оценки. Пытался использовать обычный цикл `for` по индексам, как для массива или списка, но компилятор выдаёт ошибку — оказывается, у Dictionary нет индексатора по числовому индексу. Пробовал искать в интернете, но нашёл кучу разных вариантов: `foreach`, `for` с `ElementAt()`, LINQ-запросы. Я совсем запутался. Какой способ является правильным и наиболее эффективным для простого перебора всех пар ключ-значение? И как получить доступ к ключу и значению внутри цикла? Заранее спасибо за помощь! <<>>

Основной способ: цикл foreach по KeyValuePair

Самый правильный и производительный способ перебора всех элементов словаря в C# — использование цикла foreach по коллекции KeyValuePair. Каждый элемент словаря представлен структурой KeyValuePair<TKey, TValue>, которая содержит свойства Key и Value.

Dictionary<string, int> students = new Dictionary<string, int>
{
    { "Анна", 85 },
    { "Иван", 92 },
    { "Мария", 78 }
};

// Перебор всех пар ключ-значение
foreach (KeyValuePair<string, int> student in students)
{
    Console.WriteLine($"Студент: {student.Key}, Оценка: {student.Value}");
}

Если вы используете C# 7.0 или новее, можно упростить код с помощью деконструкции кортежей:

foreach (var (name, grade) in students)
{
    Console.WriteLine($"Студент: {name}, Оценка: {grade}");
}

Альтернатива: перебор ключей или значений отдельно

Иногда нужно пройтись только по ключам или только по значениям. Для этого у Dictionary есть свойства Keys и Values:

// Только ключи
foreach (string name in students.Keys)
{
    Console.WriteLine($"Студент: {name}");
}

// Только значения
foreach (int grade in students.Values)
{
    Console.WriteLine($"Оценка: {grade}");
}

Почему не работает for по индексу?

Dictionary — это хеш-таблица, а не список с последовательными индексами. Элементы хранятся в произвольном порядке (в зависимости от хеш-функции). Хотя технически можно использовать ElementAt(index) из LINQ, это крайне неэффективно, так как при каждом обращении словарь будет заново искать элемент по индексу. Никогда так не делайте в production-коде:

// ПЛОХОЙ пример — медленно и нечитаемо
for (int i = 0; i < students.Count; i++)
{
    var pair = students.ElementAt(i); // O(n) на каждой итерации!
    Console.WriteLine($"{pair.Key}: {pair.Value}");
}

Важные замечания

  • Порядок элементов: Dictionary не гарантирует порядок хранения. Если нужен упорядоченный словарь, используйте SortedDictionary (сортировка по ключу) или OrderedDictionary (сохранение порядка вставки).
  • Изменение словаря во время перебора: Не пытайтесь добавлять или удалять элементы внутри цикла foreach — это вызовет исключение InvalidOperationException. Если нужно модифицировать словарь, сначала соберите ключи в отдельный список.
  • Производительность: foreach по словарю работает за O(n) и является оптимальным способом полного перебора.

Таким образом, запомните золотое правило: для перебора Dictionary используйте foreach (var pair in dictionary) — это просто, быстро и понятно.

<<>>

Использование LINQ для более сложных сценариев

Если вам нужно не просто вывести все элементы, а выполнить фильтрацию, проекцию или агрегацию, удобно использовать LINQ-методы в связке с Dictionary.

Например, найдём всех студентов с оценкой выше 80:

Dictionary<string, int> students = new Dictionary<string, int>
{
    { "Анна", 85 },
    { "Иван", 92 },
    { "Мария", 78 }
};

// Фильтрация по значению
var excellentStudents = students
    .Where(pair => pair.Value > 80)
    .Select(pair => pair.Key);

Console.WriteLine("Отличники: " + string.Join(", ", excellentStudents));
// Вывод: Отличники: Анна, Иван

Также можно преобразовать словарь в другой тип данных:

// Создаём список кортежей (имя, оценка, статус)
var studentReports = students
    .Select(pair => (
        Name: pair.Key, 
        Grade: pair.Value, 
        Status: pair.Value >= 80 ? "Сдал" : "Не сдал"
    ))
    .ToList();

Обратите внимание: LINQ-запросы возвращают новую коллекцию и не изменяют исходный словарь. Если вам нужно часто выполнять такие операции, возможно, стоит рассмотреть использование ConcurrentDictionary для многопоточных сценариев или специализированные структуры данных.

Главное преимущество LINQ — читаемость и гибкость, но помните о небольшом накладном расходе на создание итераторов. Для простого перебора без дополнительной логики лучше использовать обычный foreach.