Учитывая, что такие коллекции, как 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
Это может быть (немного) проблематично с перечислениями, допускающими значение NULL. Если мы определим
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
?
ИЗМЕНИТЬ / ДОБАВИТЬ:
Некоторые люди, кажется, думают, что речь идет о переопределении Object.GetHashCode()
. На самом деле это не так. (Однако авторы .NET сделали переопределение GetHashCode()
в Nullable<>
структуре, что имеет значение.) Написанная пользователем реализация без параметров GetHashCode()
никогда не сможет справиться с ситуацией, в которой находится объект, хэш-код которого мы ищем null
.
Речь идет о реализации абстрактного метода EqualityComparer<T>.GetHashCode(T)
или иной реализации метода интерфейса IEqualityComparer<T>.GetHashCode(T)
. Теперь, создавая эти ссылки на MSDN, я вижу, что там сказано, что эти методы генерируют, ArgumentNullException
если их единственный аргумент - null
. Это определенно должно быть ошибкой в MSDN? Ни одна из собственных реализаций .NET не создает исключений. Бросок в этом случае эффективно прервет любую попытку добавить null
к HashSet<>
. Если только HashSet<>
не произойдет что-то экстраординарное при работе с null
предметом (мне придется это проверить).
НОВОЕ РЕДАКТИРОВАНИЕ / ДОБАВЛЕНИЕ:
Сейчас попробовал отладку. С помощью 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;
}
}