Враховуючи, що колекції люблять 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, я бачу, що там сказано, що ці методи кидають ArgumentNullExceptionif, якщо їх єдиним аргументом є 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;
}
}