Коллекция Dictionary<K, V>
Dictionary<K, V> — класс пространства имён System.Collections.Generic
, который представляет из себя коллекцию ключей и значений, также называемый Словарем. Коллекция типизируется двумя типами: K
(key) — тип ключа и V
(value) — тип значения. Коллекция позволяет получать значения со скоростью близкой к O(1). Скорость зависит от качества алгоритма хеширования типа, заданного для ключа.
Создания и инициализация словаря
Рассмотрим способы создания и инициализации класса Dictionary<K, V>
.
Например, пустой словарь:
Dictionary<string, string> dict = new Dictionary<string, string>();
В примере выше тип ключа и значения указан string
(строки).
Рассмотрим способ, когда при инициализации мы сразу же наполняем словарь:
Dictionary<string, string> dict = new Dictionary<string, string> { { "Hello", "Привет" }, { "How are you?", "Как дела?" }, { "Bye", "Пока" } };
Каждое новое значение берётся в фигурные скобки: первое значение — ключ, второе значение — значение, которое будет доступно по ключу.
Рассмотрим ещё один способ наполнения словаря:
Dictionary<string, string> dict = new Dictionary<string, string> { ["Hello"]= "Привет", ["How are you?"] = "Как дела?", ["Bye"] = "Пока", };
KeyValuePair
По сути, словарь — это коллекция, т. е. набор элементов, тип элементов — KeyValuePair<TKey, TValue>
, где TKey
— ключ, а TValue
— значение. Данная структура предоставляет свойства Key
и Value
, с помощью которых можно получить ключ и значение. Один из конструкторов Dictionary<TKey, TValue>
принимает на входе список элементов типа KeyValuePair<TKey, TValue>
. Рассмотрим пример:
var hello = new KeyValuePair<string, string>("Hello", "Привет"); var listForDict = new List<KeyValuePair<string, string>>() { hello }; Dictionary<string, string> dict = new Dictionary<string, string>(listForDict);
В примере выше Hello — ключ, Привет — значение, которые мы передаём в конструктор KeyValuePair<string, string>
, который, в свою очередь, передает в список **listForDict
**при инициализации в фигурных скобках и далее передает в конструктор словаря dict
.
Также мы можем совместить два способа инициализации:
var hello = new KeyValuePair<string, string>("Sorry", "Извиняюсь"); var listForDict = new List<KeyValuePair<string, string>>() { hello }; Dictionary<string, string> dict = new Dictionary<string, string>(listForDict) { ["Hello"]= "Привет", ["How are you?"] = "Как дела?", ["Bye"] = "Пока", };
Больше полезных материалов вы найдете на нашем телеграм-канале «Библиотека шарписта» Интересно, перейти к каналу
Перебор словаря
Рассмотрим пример из предыдущего раздела и убедимся с помощью цикла foreach
, что в словаре действительно четыре элемента:
//Инициализируем словарь var hello = new KeyValuePair<string, string>("Sorry", "Извиняюсь"); var listForDict = new List<KeyValuePair<string, string>>() { hello }; Dictionary<string, string> dict = new Dictionary<string, string>(listForDict) { ["Hello"]= "Привет", ["How are you?"] = "Как дела?", ["Bye"] = "Пока", }; //Переберём значения словаря и выведем их foreach (var kvp in dict) Console.WriteLine($"Key:[{kvp.Key}] Value:[{kvp.Value}]");
Также можно перебрать словарь классическим циклом for
:
for (int i = 0; i < dict.Count; i++) Console.WriteLine($"Key:[{dict.ElementAt(i).Key}] Value:[{dict.ElementAt(i).Value}]");
При попытке обратиться к элементу словаря с помощью []
, мы получим ошибку (для текущего словаря). Если ключи — тип int
, то можно получить исключение KeyNotFoundException
, если бы в словаре не было соответствующего ключа.
Получение элементов
Для получения значения по ключу необходимо использовать квадратные скобки:
словарь[Ключ элемента]
Рассмотрим пример работы с элементами словаря:
Dictionary<string, string> dict = new Dictionary<string, string> { ["Hello"]= "Привет", ["How are you?"] = "Как дела?", ["Bye"] = "Пока", }; //Получим элемент по ключу Console.WriteLine(dict["Hello"]); //Изменим значение для ключа dict["Hello"] = "Здравствуйте"; Console.WriteLine(dict["Hello"]); //Добавим новое значение dict["I am fine"] = "Я в порядке"; Console.WriteLine(dict["I am fine"]);
Как можно заметить в примере выше, значение по ключу можно получить, заменить и даже создать новое.
Методы и свойства Dictionary
Методы Dictionary
Добавление:
void Add(TKey key, TValue value)
— Добавляет элемент в коллекцию с ключом key
и значением value
bool TryAdd(TKey key, TValue value)
— Метод пытается добавить новый элемент в коллекцию. Если ключ в словаре не найден, то метод ничего не сделает и вернёт false
.
Удаление:
bool Remove(TKey key)
— Удаляет элемент по ключу, при успехе возвращает — true
bool Remove(TKey key, out TValue value)
— Аналогично примеру выше, но ещё помещает значение удалённого элемента в выходной параметр value
.
void Clear()
— Очищает словарь.
Получение:
bool TryGetValue(TKey key, out TValue value)
— Пытается получить значение по ключу, при успехе возвращает true
и записывает полученное значение в переменную value
Прочее:
bool ContainsKey(TKey key)
— Проверяет наличие ключа в словаре.
bool ContainsValue(TValue value)
— Проверяет наличие значения в словаре.
int EnsureCapacity(int capacity)
— Обеспечивает возможность хранения указанного количества записей в словаре без дальнейшего увеличения его резервного хранилища. Возвращает текущее количество элементов в словаре.
void TrimExcess()
— Устанавливает ёмкость словаря такой, какой бы она была, если словарь был изначально инициализирован со всеми записями.
void TrimExcess(int capacity)
— Устанавливает ёмкость словаря такой, чтобы в нём помещалось указанное количество записей без дальнейшего увеличения его резервного хранилища. Если capacity
меньше текущей ёмкости словаря, то генерирует исключение ArgumentOutOfRangeException
.
Свойства Dictionary
int Count { get; }
— Возвращает число элементов в словаре.
IEqualityComparer<TKey> Comparer { get; }
— Возвращает интерфейс IEqualityComparer<T>
, используемый для установления равенства ключей словаря.
Dictionary<TKey, TValue>.KeyCollection Keys { get; }
— Коллекция ключей.
Dictionary<TKey, TValue>.ValueCollection Values { get; }
— Коллекция значений.
TValue this[TKey key]
— Индексатор возвращает значение по заданному ключу.
Конструкторы
Рассмотрим доступные конструкторы:
public Dictionary(int capacity, IEqualityComparer<TKey>? comparer)
— Основной конструктор, задающий размер текущего словаря и задающий объект, реализующий интерфейс IEqualityComparer
, используемый для сравнения ключей. Входная переменная capacity
, которую мы передаём, на самом деле не является реальным размером словаря. Начальный размер словаря выбирается из набора простых чисел (до 7199369) и выставляется равным или больше заданного числа, если же число элементов больше, чем максимальное число из набора, то дальше поиск размера идёт перебором с проверкой на простое значение до максимального значения int(0x7fffffff — 2147483647)
. Инициализируются массивы выбранного ранее размера под наши ключи и значения и назначается comparer
.
public Dictionary()
— Обычный конструктор без параметров, создаёт словарь, а при вызове вызывает другой конструктор с параметрами this(0, null)
.
public Dictionary(int capacity)
— Аналогично конструкторам выше вызывает основной конструктор с параметрами this(capacity, null)
.
public Dictionary(IEqualityComparer<TKey>? comparer)
— Аналогично конструктору выше вызывает основной конструктор с параметрами this(0, comparer)
.
public Dictionary(IDictionary<TKey, TValue> dictionary)
— конструктор, принимающий один параметр: объект, реализующий интерфейс IDictionary
, вызывающий другой конструктор this(dictionary, null)
, который, вызывает конструктор с передачей количества элементов и null
для comparer
. Также сохраняет все элементы из переданного объекта с помощью метода Add
.
public Dictionary(IEnumerable<KeyValuePair<TKey, TValue>> collection, IEqualityComparer<TKey>? comparer)
— вызывает основной конструктор с передачей количества записей и comparer
— this((collection as ICollection<KeyValuePair<TKey, TValue>>)?.Count ?? 0, comparer)
, проверяет коллекцию на null
. Если null
, то выбрасывает исключение ArgumentNullException
и заносит все значения с помощью метода AddRange(collection)
.
public Dictionary(IDictionary<TKey, TValue> dictionary, IEqualityComparer<TKey>? comparer)
— вызывает основной конструктор с параметрами this(dictionary != null ? dictionary.Count : 0, comparer)
и проверяет словарь на null
, если null
, то выбрасывает исключение ArgumentNullException
и заносит все значения с помощью метода AddRange(dictionary)
.
public Dictionary(IEnumerable<KeyValuePair<TKey, TValue>> collection)
— вызывает другой конструктор с параметрами this(collection, null)
.
Явные реализации интерфейса
ICollection.CopyTo(Array, Int32)
— Копирует элементы коллекции ICollection<T>
в массив, начиная с указанного индекса массива.
ICollection.IsSynchronized
— Получает значение, определяющее, является ли доступ к коллекции ICollection
синхронизированным (потокобезопасным).
ICollection.SyncRoot
— Получает объект, с помощью которого можно синхронизировать доступ к коллекции ICollection
.
IDictionary.Add(Object, Object)
— Добавляет указанные ключ и значение в словарь.
IDictionary.Contains(Object)
— Определяет, содержится ли элемент с указанным ключом в IDictionary
.
IDictionary.Keys
— Возвращает интерфейс ICollection
, содержащий ключи IDictionary
.
IDictionary.Values
— Возвращает интерфейс ICollection
, содержащий значения из IDictionary
.
Методы расширения
Рассмотрим методы расширения, которые имеют отношение непосредственно к словарю. В реальности их гораздо больше, а, так как словарь реализовывает такие интерфейсы как ICollection
, IEnumerable
, IDeserializationCallback
их ещё больше (больше информации).
Remove<TKey,TValue>(IDictionary<TKey,TValue>, TKey, TValue)
— Пытается удалить значение с указанным key
из dictionary
.
GetValueOrDefault<TKey,TValue>(IReadOnlyDictionary<TKey,TValue>, TKey)
— Пытается получить значение, связанное с указанным key
в dictionary
.
TryAdd<TKey,TValue>(IDictionary<TKey,TValue>, TKey, TValue)
— Пытается добавить указанные элементы key
и value
в dictionary
.
Примеры
Самый наглядный вариант реализации словаря — англо-русский словарь, но мы рассмотрим менее классические примеры:
// Допустим используем словарь для хранения номеров, но помним, // что ключ должен быть уникальным, иначе мы получим ошибку уникальных // значений. Dictionary<string, long> phones = new Dictionary<string, long> { { "Вася", 81112223344 }, { "Петя", 82224445566 }, { "Стёпа", 83335556677 } }; // Допустим у нас есть метод отправки смс по номеру void SendSMS(long phone, string msg) { // todo } // Допустим мы хотим стёпе отправить смс, получаем его номер по имени SendSMS(phones["Стёпа"], "Привет, как дела?"); // Допустим мы получили ответное смс var sms = GetSMS(phones["Стёпа"], "Привет, всё ок, а у тебя?"); // Воспользуемся методом, который позволит определить от кого смс и что пишет Console.WriteLine($"{WhoSendSMS(sms.phone)} - {sms.msg}" ); Console.WriteLine(); sms = GetSMS(81111111111, "Здравствуйте вам одобрена..."); // Получим смс от кого-то ещё) Console.WriteLine($"{WhoSendSMS(sms.phone)} - {sms.msg}"); Console.WriteLine(); // Допустим нам не понравился этот номер и мы не хотим больше от него получать смс // создадим ещё один словарь, с помощью него будем блокировать номера телефонов // и добавим туда наш новый номер с флагом false Dictionary<long, bool> accessNumbers = new Dictionary<long, bool> { { 81111111111, false } }; // Также добавим туда все номера из нашей записной книжки с флагом true foreach (var phone in phones) accessNumbers[phone.Value] = true; // Попробуем снова получить СМС var newSms = GetSMSWithAccess(81111111111, "Здравствуйте вам одобрена..."); // Поскольку теперь нам может прийти null нужно проверить есть ли действительно // смс if (newSms != null) Console.WriteLine($"{WhoSendSMS(newSms.phone)} - {newSms.msg}"); else Console.WriteLine($"Пришла СМС от заблокированного номера."); Console.WriteLine(); // Ну и проверим сообщение от Васи например newSms = GetSMSWithAccess(phones["Вася"], "Привет"); if (newSms != null) Console.WriteLine($"{WhoSendSMS(newSms.phone)} - {newSms.msg}"); Console.WriteLine(); // Допустим хотим посмотреть все номера в нашей записной книжке Console.WriteLine($"Всего номеров в записной книжке: {phones.Count}"); foreach (var phone in phones) Console.WriteLine($"{phone.Key} - {phone.Value}"); // Допустим мы видим, что у нас записан Петя, с которым давно // не общались и хотим удалить его номер Console.WriteLine(); phones.Remove("Петя"); Console.WriteLine($"Всего номеров в записной книжке: {phones.Count}"); // Проверим снова телефонную книгу foreach (var phone in phones) Console.WriteLine($"{phone.Key} - {phone.Value}"); Console.WriteLine(); // Как насчёт информации о доступных номерах? Console.WriteLine($"Всего записей в коллекции с доступными номерами: {accessNumbers.Count}"); foreach (var phone in accessNumbers) Console.WriteLine($"От номера {phone.Key} получать смс {(phone.Value ? "можно" : "нельзя")}."); // Допустим есть метод, который возвращает нам текст сообщения и номер телефона SMSInfo GetSMS(long phone, string msg) { return new SMSInfo { phone = phone, msg = msg }; } SMSInfo? GetSMSWithAccess(long phone, string msg) { if (accessNumbers[phone]) return new SMSInfo { phone = phone, msg = msg }; return null; } // А теперь попробуем найти обратно, кому же принадлежит номер string WhoSendSMS(long phone) { // Переберём все значения в словаре и найдём имя отправителя foreach (var phoneInfo in phones) { if (phoneInfo.Value == phone) return phoneInfo.Key; } return "Номер отсутствует в записной книжке!"; } // Для следующего примера создадим специальный класс class SMSInfo { public long phone { get; set; } public string msg { get; set; } }
Комментарии
Как было описано выше, скорость доступа к значениям словаря близка к O(1). Скорость зависит от качества алгоритма хеширования указанного типа TKey
. Значения ключей должны быть уникальными. Dictionary<TKey,TValue>
требует реализации равенства, чтобы определить, равны ли ключи. Можно указать реализацию универсального интерфейса с помощью конструктора, принимающего **IEqualityComparer<T>** **comparer**
параметр.
Например, можно использовать компараторы строк без учёта регистра, предоставляемые классом StringComparer
, для создания словарей с нечувствительными к регистру строковыми ключами.
Ёмкость a Dictionary<TKey,TValue>
— это количество элементов, которые Dictionary<TKey,TValue>
могут храниться. При добавлении элементов к объекту Dictionary<TKey,TValue>
ёмкость автоматически увеличивается, так как требуется перераспределить внутренний массив.
Итог
Словарь используется для быстрого доступа к значениям по ключу и содержит значения, которые можно получить по уникальным ключам. Имеет смысл использовать словарь, если есть большая коллекция данных, в которой часто производится поиск значений по какому-то ключу.
***
Материалы по теме
👨🎓️ Самоучитель по C# для начинающих. Часть 1: установите среду разработки и освойте основы языка за 30 минут
👨🎓️ Самоучитель по C# для начинающих за 30 минут. Часть 2: ООП и коллекции
🧊 Руководство по С# для начинающих: массивы и цикл foreach