Враховуючи, що колекції люблять System.Collections.Generic.HashSet<>
приймати 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
?
РЕДАГУВАТИ / ДОДАТИ:
Деякі люди, здається, думають, що мова йде про перевизначення Object.GetHashCode()
. Насправді це не так. (Однак автори .NET зробили заміну GetHashCode()
в Nullable<>
структурі, яка є релевантною.) Реалізація написаного користувачем параметра без параметрівGetHashCode()
ніколи не може впоратись із ситуацією, коли знаходиться об’єкт, чий хеш-код ми шукаємо null
.
Мова йде про реалізацію абстрактного методу EqualityComparer<T>.GetHashCode(T)
або про інший спосіб реалізації методу інтерфейсу IEqualityComparer<T>.GetHashCode(T)
. Тепер, створюючи ці посилання на MSDN, я бачу, що там сказано, що ці методи кидають ArgumentNullException
if, якщо їх єдиним аргументом є 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;
}
}