Ответ
Да, для корректной работы Dictionary<TKey, TValue> к типу ключа предъявляются строгие требования, вытекающие из его внутренней хеш-табличной реализации.
Основные ограничения и требования:
- Уникальность: Ключи в рамках одного словаря должны быть уникальными. Попытка добавить дубликат выбросит
ArgumentException. - Консистентность
GetHashCode()иEquals(): Это самое важное требование.GetHashCode()должен возвращать одно и то же значение для одного и того же объекта на протяжении всего времени его жизни (пока объект используется как ключ).Equals()должен быть логически согласован сGetHashCode(): еслиEquals()возвращаетtrueдля двух объектов, то иGetHashCode()для них должен возвращать одинаковое значение. Обратное не обязательно.
- Неизменяемость (рекомендация): Ключ должен быть неизменяемым по значению после добавления в словарь. Если свойство, участвующее в
GetHashCode()/Equals(), изменить, ключ "потеряется" в словаре, так как будет искаться по новому хеш-коду.
Пример проблем с изменяемым ключом:
public class MutableKey
{
public int Id { get; set; }
public override int GetHashCode() => Id.GetHashCode();
public override bool Equals(object obj) => obj is MutableKey other && Id == other.Id;
}
var dict = new Dictionary<MutableKey, string>();
var key = new MutableKey { Id = 1 };
dict[key] = "Value 1";
// Меняем ключ ПОСЛЕ добавления в словарь — КАТАСТРОФА!
key.Id = 2;
// Попытка найти значение по старому ключу (Id=1) не сработает
bool found = dict.TryGetValue(new MutableKey { Id = 1 }, out var value); // found = false
// Попытка найти по новому ключу (Id=2) тоже, скорее всего, не сработает,
// потому что объект лежит в другой "корзине" хеш-таблицы.
Лучшие практики для ключей:
- Используйте встроенные неизменяемые типы:
string,int,Guid,DateTimeи т.д. - Используйте
recordтипы (начиная с C# 9), которые по умолчанию предоставляют корректную реализациюGetHashCode()иEquals()на основе значений. - Если создаёте свой класс для ключа, сделайте его неизменяемым (только для чтения свойства,
init-аксессоры) и всегда корректно переопределяйтеGetHashCode()иEquals().
// Идеальный ключ с использованием record (неизменяемый и корректный)
public record PersonKey(string Name, int BirthYear);
var dictionary = new Dictionary<PersonKey, string>();
dictionary[new PersonKey("Alice", 1990)] = "Developer";
// Безопасно и надёжно.