Учитывая, что такие коллекции, как 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;
}
}