1. Введение
Давайте представим: вы устраиваетесь на работу в секретную лабораторию по изучению котиков. Вам дают задачу — создать электронный словарь котиков, где по кличке можно быстро узнать возраст, цвет и размер хвоста кота. Готовое решение уже есть — это Dictionary<string, Cat>. Но вот беда: позднее начальник говорит, что для некоторых редких пород нужно, чтобы словарь поддерживал сохранение порядка добавления, а иногда ещё и удалялся по особой логике.
А что если вам нужно написать метод, который будет принимать любой «кошачий словарь»? Неважно, какой конкретно класс — важно, чтобы он поддерживал основные операции «ключ-значение». Вот здесь вступает в игру интерфейс IDictionary<TKey, TValue>.
IDictionary<TKey, TValue> — это общий контракт, который определяет, что такое словарь в .NET. Это не конкретный класс, а интерфейс, то есть набор правил, которые должен поддерживать любой настоящий словарь:
- Быстрый поиск по ключу
- Добавление, удаление пар «ключ-значение»
- Перебор всех пар
- Проверка наличия ключа
Если бы Java существовала в Средневековье, её рыцари вместо мечей против драконов размахивали бы ключами и значениями. А C#-разработчики просто реализовали бы IDictionary<TKey, TValue> — и дракон (то есть ваш код) был бы повержен.
2. Таблица интерфейса IDictionary
Давайте посмотрим на основные "обещания" (члены) этого интерфейса:
| Член интерфейса | Тип | Описание |
|---|---|---|
|
Свойство | Индексатор. Позволяет получать или устанавливать значение, связанное с указанным ключом. При чтении: если ключ не найден, выбрасывает KeyNotFoundException. При записи: если ключ не найден, добавляет новую пару; если ключ существует, обновляет значение. Важно: это самый частый способ работы со словарями. |
|
Свойство | Возвращает коллекцию, содержащую все ключи в словаре. Позволяет перебирать только ключи. |
|
Свойство | Возвращает коллекцию, содержащую все значения в словаре. Позволяет перебирать только значения. |
|
Метод | Добавляет указанный ключ и значение в словарь. Если ключ уже существует, генерирует ArgumentException. |
|
Метод | Определяет, содержит ли словарь элемент с указанным ключом. Возвращает true, если ключ найден; в противном случае — false. Это очень полезно, чтобы избежать ошибок при попытке обратиться к несуществующему ключу через индексатор. |
|
Метод | Удаляет элемент с указанным ключом из словаря. Возвращает true, если элемент успешно найден и удален; в противном случае — false (например, если ключ не был найден). |
|
Метод | Получает значение, связанное с указанным ключом. Это безопасный способ получить значение, если вы не уверены, существует ли ключ. Возвращает true, если ключ найден, и value содержит соответствующее значение; в противном случае — false, и value будет содержать значение по умолчанию для TValue. Этот метод не выбрасывает исключение, что делает его очень популярным в реальном коде. |
3. Основные члены интерфейса IDictionary<TKey, TValue>
А теперь — к самому важному! Посмотрим, что внутри интерфейса IDictionary<TKey, TValue>, и научимся пользоваться этим «контрактом» в вашем коде.
public interface IDictionary<TKey, TValue> : ICollection<KeyValuePair<TKey, TValue>>, IEnumerable<KeyValuePair<TKey, TValue>>
{
TValue this[TKey key] { get; set; } // Индексатор для доступа по ключу
ICollection<TKey> Keys { get; }
ICollection<TValue> Values { get; }
void Add(TKey key, TValue value); // Добавить новую пару (ключ не должен существовать)
bool ContainsKey(TKey key); // Есть ли такой ключ?
bool Remove(TKey key); // Удалить по ключу
bool TryGetValue(TKey key, out TValue value); // Безопасно получить значение
}
Давайте разберём основные моменты:
Индексатор [key]
Позволяет получать и задавать значения по ключу:
dictionary["Барсик"] = new Cat("Барсик", 2);
Свойства Keys и Values
Позволяют получить коллекцию всех ключей или всех значений в словаре.
foreach (var name in dictionary.Keys)
{
Console.WriteLine(name);
}
Методы Add, Remove, ContainsKey, TryGetValue
- Add(key, value) — добавить новую пару
- Remove(key) — удалить по ключу
- ContainsKey(key) — проверить наличие ключа
- TryGetValue(key, out value) — безопасно получить значение (без исключения, если ключа нет)
4. Использование IDictionary<TKey, TValue> на практике
Универсальные методы
Допустим, вы пишете функцию, которая должна работать со словарём, но не хочет знать, какой класс внутри: обычный Dictionary, SortedDictionary, или вдруг кто-то реализовал свой собственный «криптоколлективный словарь».
Вы объявляете параметр типа IDictionary<TKey, TValue>:
static void PrintDictionary<TKey, TValue>(IDictionary<TKey, TValue> someDictionary)
{
foreach (var pair in someDictionary)
{
Console.WriteLine($"{pair.Key}: {pair.Value}");
}
}
Теперь ваш метод принимает любой словарь! Под капотом может быть что угодно — даже словарь, который шифрует значения, если вам настолько весело в жизни.
Использование IDictionary для передачи параметров
Допустим, вы делаете приложение для учёта котиков, и хотите, чтобы ваш метод работал со всеми возможными словарями настроек, а не только с одним конкретным классом. Вот как это выглядит:
void SetCatParameters(IDictionary<string, string> parameters)
{
if (parameters.ContainsKey("color"))
{
Console.WriteLine($"Покрасьте котика в цвет: {parameters["color"]}");
}
}
Единая сигнатура — несколько реализаций
Поясним на живой таблице:
| Класс коллекции | Реализует IDictionary<TKey,TValue> | Особенности |
|---|---|---|
|
✅ Да | Быстрый поиск, ключи в произвольном порядке |
|
✅ Да | Ключи автоматически сортируются |
|
✅ Да | Ключи сортируются, более экономно по памяти |
| (Свой класс, реализующий интерфейс) | ✅ Да | Любая ваша логика, но обязаны соблюдать контракт |
5. Связь с другими интерфейсами коллекций
Для настоящих гиков: интерфейс IDictionary<TKey, TValue> наследуется от ICollection<KeyValuePair<TKey, TValue>> и IEnumerable<KeyValuePair<TKey, TValue>>. Это значит, что любой словарь можно:
- Перебирать через foreach по парам ключ-значение,
- Добавлять и удалять пары с помощью методов коллекции,
- Получать количество элементов.
foreach (var entry in myDictionary)
{
Console.WriteLine($"{entry.Key} => {entry.Value}");
}
6. Особенности и типичные ошибки
Работа с индексатором
Самая частая ошибка новичков: попытка получить элемент по отсутствующему ключу вызовет исключение KeyNotFoundException.
var value = myDictionary["НемаТакогоКлюча"]; // Boom!
Поэтому всегда лучше использовать TryGetValue:
if (myDictionary.TryGetValue("Мурзик", out var cat))
{
Console.WriteLine($"Нашли котика: {cat}");
}
else
{
Console.WriteLine($"Котика не найдено!");
}
Добавление уже существующего ключа
Если вызвать Add для ключа, который уже есть, получите ArgumentException. Если хотите «добавлять или обновлять», используйте индексатор:
// Добавляет если нет, обновляет если есть
myDictionary["Мурка"] = new Cat("Мурка", 5);
Перебор и модификация
Не пытайтесь модифицировать словарь (например, удалять элементы) в цикле foreach напрямую: получите исключение. Если нужно удалить что-то, сначала соберите список ключей для удаления, а потом удаляйте их в отдельном цикле:
// Безопасный способ удаления элементов
var keysToRemove = new List<string>();
foreach (var pair in myDictionary)
{
if (pair.Value.Age > 10) // Условие для удаления
{
keysToRemove.Add(pair.Key);
}
}
foreach (var key in keysToRemove)
{
myDictionary.Remove(key);
}
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ