1. Коллекция OrderedDictionary
В .NET всегда ценили богатую стандартную библиотеку, однако в жизни программиста случались ситуации с компромиссами: хочется словарь, который запоминает порядок добавления элементов, или нужно набор неизменяемых уникальных значений. До .NET 9 приходилось использовать сторонние библиотеки либо изобретать свои велосипеды (кто-то даже открывал GitHub и копировал OrderedDictionary оттуда — ш-ш-ш, мы никому не расскажем).
С выходом .NET 9 появились новые универсальные коллекции — теперь можно забыть про костыли! Давайте подробно разберём две самые полезные из них: OrderedDictionary<TKey, TValue> и ReadOnlySet<T>.
1. Что такое OrderedDictionary?
OrderedDictionary — это гибрид словаря и списка. Он хранит пары "ключ-значение", как обычный Dictionary<TKey, TValue>, но гарантирует, что порядок элементов соответствует порядку их добавления. Это особенно важно для задач, где нужно обходить элементы в том же порядке, в каком пользователь их вводил, или когда порядок влияет на бизнес-логику: печать отчётов, сериализацию данных, генерацию конфигураций.
АналогияЕсли обычный словарь — это шкаф с кучей ячеек, куда можно быстро засовывать и доставать вещи, не думая о порядке, то OrderedDictionary — это аккуратный шкафчик с выдвижными ящиками. В нём всё лежит по порядку, и вы всегда знаете, что положили сначала, а что потом.
2. Главное отличие от Dictionary
- Dictionary: порядок элементов не гарантируется (даже если кажется, что всё получается одинаково — не верьте, это иллюзия!).
- OrderedDictionary: элементы хранятся именно в том порядке, как были добавлены.
3. Синтаксис и основные методы
Вот базовый пример использования:
using System.Collections.Generic;
var od = new OrderedDictionary<string, int>();
od.Add("Ivan", 5);
od.Add("Svetlana", 8);
od.Add("Alex", 3);
// Перебор по порядку добавления:
foreach (var pair in od)
{
Console.WriteLine($"{pair.Key}: {pair.Value}");
}
Вывод:
Ivan: 5
Svetlana: 8
Alex: 3
Если попробовать тот же пример с обычным словарем, порядок зачастую будет другим. А вот с OrderedDictionary — всегда гарантированно так, как вы добавили.
OrderedDictionary реализует те же интерфейсы, что и обычный словарь:
- IDictionary<TKey, TValue>
- IReadOnlyDictionary<TKey, TValue>
- IEnumerable<KeyValuePair<TKey, TValue>>
4. Доступ по индексу и по ключу
Особенность: у OrderedDictionary есть индексатор по ключу и по порядковому индексу!
// По имени (ключу):
int svetlanaScore = od["Svetlana"]; // 8
// По индексу:
var firstEntry = od.ElementAt(0); // KeyValuePair<string, int>("Ivan", 5)
5. Обновление и удаление
Если вы добавляете новый элемент с существующим ключом, выбрасывается исключение. Если хотите заменить значение — используйте индексатор:
od["Ivan"] = 10; // Изменит существующий, порядок не изменится
Удалять можно как по ключу, так и по индексу:
od.Remove("Svetlana");
od.RemoveAt(0); // удаляет "Ivan"
6. Визуализация структуры
flowchart LR
A("0: Ivan - 5")
B("1: Svetlana - 8")
C("2: Alex - 3")
A --> B --> C
Каждый узел — это пара ключ-значение, а стрелки показывают порядок добавления.
7. Подводные камни и ошибки
Многие разработчики пытаются использовать Dictionary<TKey, TValue> и надеются на порядок — но это ловушка! Даже если на одних данных порядок сохраняется, в следующий раз он изменится (особенно при изменениях .NET или на другой платформе).
В OrderedDictionary из-за хранения порядка минимально медленнее некоторые операции (вставка, удаление из середины), чем в обычном словаре, но для подавляющего большинства задач преимущества перевешивают.
Если вам нужно часто искать по индексу — это как раз про OrderedDictionary, а если нужен только быстрый поиск по ключу и неважен порядок — используйте обычный словарь.
8. Практический пример для приложения
Допустим, в нашем приложении по учёту сотрудников и отделов теперь нужен отчёт, где сотрудники выводятся в том порядке, в котором были добавлены:
var employeeScores = new OrderedDictionary<string, int>();
employeeScores.Add("Петр", 100);
employeeScores.Add("Анна", 150);
employeeScores.Add("Виктория", 80);
// Теперь отчёт всегда в нужном порядке:
foreach (var pair in employeeScores)
{
Console.WriteLine($"{pair.Key}: {pair.Value} баллов");
}
2. Коллекция ReadOnlySet<T>
1. Что такое ReadOnlySet?
ReadOnlySet<T> — это неизменяемое (immutable) множество уникальных значений. Раньше, чтобы создать "сет" только для чтения, приходилось возвращать копию через .ToHashSet() или создавать свою обёртку. Теперь есть коллекция, в которую нельзя добавить или удалить элемент после создания. Это повышает безопасность кода и предотвращает случайные ошибки при изменении данных извне.
АналогияЭто как блокнот, где вы записали уникальные имена и… заламинировали страницы. Никто больше ничего не впишет, не вырвет и не исправит.
2. Создание ReadOnlySet
Самый простой способ создать неизменяемое множество — воспользоваться методом расширения:
var colors = new[] { "Red", "Green", "Blue", "Green" };
var readOnlyColors = colors.ToReadOnlySet();
// Теперь тут строго только уникальные значения и изменить нельзя:
foreach (var color in readOnlyColors)
Console.WriteLine(color);
Вывод:
Red
Green
Blue
Можно использовать и прямой конструктор (если нужно):
var set = new ReadOnlySet<int>(new[] {1, 2, 2, 3, 5, 1}); // Всё равно будет 1,2,3,5
3. Основные свойства и методы
- Только чтение (immutable)
- Count — количество элементов
- Contains(item) — проверка наличия элемента
- Поддерживает LINQ-запросы (IEnumerable<T>)
- Быстро ищет, но не поддерживает добавление, удаление, очистку
Пример:
if (readOnlyColors.Contains("Red"))
Console.WriteLine("Красный есть в списке!");
Нельзя сделать так:
// Ошибка компиляции!
readOnlyColors.Add("Yellow");
4. Где это пригодится?
Очень часто вам нужно дать наружу информацию в виде множества, но не хотите, чтобы кто-то мог случайно (или злонамеренно) его изменить. Например:
- Возвращаем перечень поддерживаемых ролей пользователя
- Список допустимых расширений файлов
- Перечень уникальных параметров конфигурации, которые нельзя изменять
Пример в нашем приложении: допустим, есть перечень допустимых отделов:
public static ReadOnlySet<string> Departments { get; } =
new[] { "Кадры", "Разработка", "Бухгалтерия" }.ToReadOnlySet();
Теперь никто не подменит список отделов снаружи.
5. Визуализация: отличие от обычного HashSet
graph LR
A[HashSet] -- Add/Remove/Contains --> B((Elements))
C[ReadOnlySet] -- Only Contains --> D((Elements))
3. OrderedDictionary vs. ReadOnlySet: таблица отличий
| Коллекция | Хранит пары? | Гарантирует порядок | Можно изменять | Поиск по ключу | Поиск по индексу | Сценарии |
|---|---|---|---|---|---|---|
| OrderedDictionary | Да "ключ-значение" | Да | Да | Да | Да | Карты, настройки, отчёты |
| ReadOnlySet | Нет, только значения | Нет | Нет | Да | Нет | Безопасные множества, константы |
| HashSet | Нет, только значения | Нет | Да | Да | Нет | Множества, где нужна модификация |
| Dictionary | Да "ключ-значение" | Нет | Да | Да | Нет | Карты без требований к порядку |
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ