Учитывая, что такие коллекции, как System.Collections.Generic.HashSet<>
accept null
как член набора, можно спросить, каков должен быть хэш-код null
. Похоже, что в структуре используется 0
:
// nullable struct type
int? i = null;
i.GetHashCode(); // gives 0
EqualityComparer<int?>.Default.GetHashCode(i); // gives 0
// class type
CultureInfo c = null;
EqualityComparer<CultureInfo>.Default.GetHashCode(c); // gives 0
Это может быть (немного) проблематично с нулевыми перечислениями. Если мы определим
enum Season
{
Spring,
Summer,
Autumn,
Winter,
}
то Nullable<Season>
(также называемый Season?
) может принимать только пять значений, но два из них, а именно null
и Season.Spring
, имеют одинаковый хэш-код.
Возникает соблазн написать "лучшее" сравнительное равенство:
class NewNullEnumEqComp<T> : EqualityComparer<T?> where T : struct
{
public override bool Equals(T? x, T? y)
{
return Default.Equals(x, y);
}
public override int GetHashCode(T? x)
{
return x.HasValue ? Default.GetHashCode(x) : -1;
}
}
Но есть ли причина, по которой хэш-код null
должен быть 0
?
EDIT/Сложение:
Некоторые люди, похоже, думают, что это касается переопределения Object.GetHashCode()
. На самом деле это не так. (Авторы .NET сделали переопределение GetHashCode()
в структуре Nullable<>
, которая имеет .) Пользовательская реализация без параметров GetHashCode()
никогда не сможет справиться с ситуацией где объект, чей хэш-код мы ищем, null
.
Речь идет об использовании абстрактного метода EqualityComparer<T>.GetHashCode(T)
или иным способом реализации метода интерфейса IEqualityComparer<T>.GetHashCode(T)
, Теперь, создавая эти ссылки на MSDN, я вижу, что там говорится, что эти методы бросают ArgumentNullException
, если их единственным аргументом является null
. Это, безусловно, ошибка в MSDN? Ни одна из собственных реализаций .NET не выдает исключений. Бросание в этом случае эффективно нарушит любую попытку добавить null
к HashSet<>
. Если HashSet<>
делает что-то необычное при работе с элементом null
(мне нужно будет это проверить).
NEW EDIT/ADDITION:
Теперь я попробовал отладку. С помощью HashSet<>
я могу подтвердить, что с помощью сопоставителя равенства по умолчанию значения Season.Spring
и null
будут заканчиваться в том же ведре. Это можно определить, тщательно проверив частные элементы массива m_buckets
и m_slots
. Обратите внимание, что индексы всегда, по дизайну, смещены на один.
Однако приведенный выше код не исправляет это. Как оказалось, HashSet<>
никогда не спросит о равенстве, когда значение null
. Это из исходного кода HashSet<>
:
// Workaround Comparers that throw ArgumentNullException for GetHashCode(null).
private int InternalGetHashCode(T item) {
if (item == null) {
return 0;
}
return m_comparer.GetHashCode(item) & Lower31BitMask;
}
Это означает, что , по крайней мере, для HashSet<>
, даже не возможно изменить хэш null
. Вместо этого решение должно изменить хэш всех других значений, например:
class NewerNullEnumEqComp<T> : EqualityComparer<T?> where T : struct
{
public override bool Equals(T? x, T? y)
{
return Default.Equals(x, y);
}
public override int GetHashCode(T? x)
{
return x.HasValue ? 1 + Default.GetHashCode(x) : /* not seen by HashSet: */ 0;
}
}