Учитывая, что такие коллекции, как 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;
}
}
Ответы:
Если хэш-код, возвращаемый для значений NULL, соответствует типу, все будет в порядке. Единственное требование для хэш-кода - это то, что два объекта, которые считаются равными, имеют один и тот же хэш-код.
Возврат 0 или -1 для null, если вы выбираете один и все время возвращаете его, будет работать. Очевидно, что ненулевые хэш-коды не должны возвращать любое значение, которое вы используете для null.
Похожие вопросы:GetHashCode для пустых полей?
Что должен вернуть GetHashCode, если идентификатор объекта равен нулю?
«Примечания» этой записи MSDN более подробно относятся к хэш-коду. Любопытно отметить , что документация не дает никакого освещения или обсуждение значений нуля на всех - даже не в содержании сообщества.Чтобы решить проблему с перечислением, либо повторно реализуйте хэш-код, чтобы он возвращал ненулевое значение, добавьте запись перечисления по умолчанию «unknown», эквивалентную null, либо просто не используйте перечисления, допускающие значение NULL.
Кстати, интересная находка.
Другая проблема, с которой я обычно сталкиваюсь, заключается в том, что хеш-код не может представлять тип размером 4 байта или более, допускающий значение NULL, без хотя бы одного столкновения (больше по мере увеличения размера типа). Например, хэш-код int - это просто int, поэтому он использует полный диапазон int. Какое значение в этом диапазоне вы выбираете для null? Какой бы из них вы ни выбрали, он столкнется с самим хеш-кодом значения.
Столкновения сами по себе не обязательно являются проблемой, но вы должны знать, что они есть. Хеш-коды используются только в некоторых случаях. Как указано в документации MSDN, хэш-коды не гарантируют возврат разных значений для разных объектов, поэтому ожидать этого не следует.
источник
Object.GetHashCode()
в своем собственном классе (или структуре), вы знаете, что этот код будет задействован только тогда, когда у людей действительно есть экземпляр вашего класса. Такого экземпляра быть не можетnull
. Вот почему вы не начинаете переопределениеObject.GetHashCode()
с.if (this == null) return -1;
Существует разница между «бытьnull
» и «быть объектом, обладающим некоторыми полями, которые естьnull
».T
, тогда(T?)null
и(T?)default(T)
будет иметь один и тот же хэш-код (в текущей реализации .NET). Это можно было бы изменить, если бы разработчики .NET изменили либо хэш-код,null
либо алгоритм хэш-кода платформыSystem.Enum
.HashSet<>
) поменять хеш-кодnull
.Имейте в виду, что хэш-код используется только в качестве первого шага при определении равенства, и никогда (не должен) использоваться как фактическое определение того, равны ли два объекта.
Если хэш-коды двух объектов не равны, они рассматриваются как неравные (потому что мы предполагаем, что базовая реализация верна - т.е. мы не догадываемся об этом). Если у них один и тот же хэш-код, тогда они должны быть проверены на фактическое равенство, которое в вашем случае
null
не удастся и значение перечисления.В результате - использование нуля так же хорошо, как и любое другое значение в общем случае.
Конечно, будут ситуации, такие как ваше перечисление, где этот ноль используется совместно с хеш-кодом реального значения. Вопрос в том, вызывает ли у вас незначительные накладные расходы на дополнительное сравнение.
Если это так, тогда определите свой собственный компаратор для случая обнуляемого для вашего конкретного типа и убедитесь, что нулевое значение всегда дает хэш-код, который всегда один и тот же (конечно!), И значение, которое не может быть получено базовым собственный алгоритм хэш-кода типа. Для ваших собственных типов это выполнимо. Для остальных - удачи :)
источник
Оно не обязательно должно быть нулевым - вы можете получить 42, если хотите.
Все, что имеет значение, - это последовательность во время выполнения программы.
Это просто наиболее очевидное представление, потому что
null
внутренне оно часто представляется как ноль. Это означает, что если во время отладки вы увидите нулевой хэш-код, это может побудить вас подумать: «Хм ... это проблема с нулевой ссылкой?»Обратите внимание: если вы используете такое число
0xDEADBEEF
, то кто-то может сказать, что вы используете магическое число ... и вы вроде бы так и сделали. (Вы могли бы сказать, что ноль - это тоже магическое число, и вы были бы правы ... за исключением того, что оно настолько широко используется, что является своего рода исключением из правила.)источник
Хороший вопрос.
Я просто попытался закодировать это:
enum Season { Spring, Summer, Autumn, Winter, }
и выполните это так:
Season? v = null; Console.WriteLine(v);
он возвращается
null
если я сделаю, вместо этого нормально
Season? v = Season.Spring; Console.WriteLine((int)v);
он вернет
0
, как и ожидалось, или просто Spring, если мы избегаем приведения вint
.Итак .. если вы сделаете следующее:
Season? v = Season.Spring; Season? vnull = null; if(vnull == v) // never TRUE
РЕДАКТИРОВАТЬ
Из MSDN
Если два объекта сравниваются как равные, метод GetHashCode для каждого объекта должен возвращать одно и то же значение. Однако, если два объекта не сравниваются как равные, методы GetHashCode для двух объектов не должны возвращать разные значения.
Другими словами: если два объекта имеют одинаковый хэш-код, это не означает, что они равны, потому что реальное равенство определяется Equals .
Снова из MSDN:
источник
Это могло быть что угодно. Я склонен согласиться с тем, что 0 не обязательно был лучшим выбором, но он, вероятно, приводит к наименьшему количеству ошибок.
Хеш-функция обязательно должна возвращать один и тот же хеш для одного и того же значения. После того, как существует в компонент , который делает это, это действительно единственное допустимое значение для хэш
null
. Если бы для этого была константа, например, хм,object.HashOfNull
то кто-то, реализующий an,IEqualityComparer
должен был бы знать, как использовать это значение. Я полагаю, что если они не задумаются об этом, то шанс, что они воспользуются 0, немного выше, чем любое другое значение.Как упоминалось выше, я думаю, что полная остановка невозможна просто потому, что существуют типы, которые уже следуют соглашению о том, что хэш нулевого значения равен 0.
источник
EqualityComparer<T>.GetHashCode(T)
для некоторого конкретного типа,T
который позволяетnull
, он должен что- то делать, когда аргумент естьnull
. Вы можете (1) броситьArgumentNullException
, (2) вернуть0
или (3) вернуть что-то еще. Я принимаю ваш ответ за рекомендацию всегда возвращаться0
в такой ситуации?Это 0 для простоты. Такого жесткого требования нет. Вам нужно только обеспечить общие требования к хэш-кодированию.
Например, вам нужно убедиться, что если два объекта равны, их хэш-коды тоже должны быть одинаковыми. Следовательно, разные хэш-коды всегда должны представлять разные объекты (но это не обязательно верно, наоборот: два разных объекта могут иметь один и тот же хэш-код, даже если это часто случается, то это хеш-функция плохого качества - у нее нет хорошая стойкость к столкновениям).
Конечно, я ограничился ответом на требования математического характера. Также существуют технические условия для .NET, с которыми вы можете ознакомиться здесь . 0 для нулевого значения среди них нет.
источник
Так что этого можно было бы избежать, используя
Unknown
значение перечисления (хотя кажется немного странным, чтобы aSeason
было неизвестно). Что-то вроде этого может свести на нет эту проблему:public enum Season { Unknown = 0, Spring, Summer, Autumn, Winter } Season some_season = Season.Unknown; int code = some_season.GetHashCode(); // 0 some_season = Season.Autumn; code = some_season.GetHashCode(); // 3
Тогда у вас будут уникальные значения хэш-кода для каждого сезона.
источник
Лично я нахожу использование значений, допускающих значение NULL, немного неудобным и стараюсь избегать их, когда могу. Ваша проблема - это еще одна причина. Иногда они очень удобны, но мое практическое правило - не смешивать типы значений с null, если это возможно, просто потому, что они из двух разных миров. В платформе .NET они, похоже, делают то же самое - многие типы значений предоставляют
TryParse
метод, который является способом отделения значений от значения без значения (null
).В вашем конкретном случае от проблемы легко избавиться, потому что вы работаете со своим собственным
Season
типом.(Season?)null
Для меня это означает, что «сезон не указан», например, когда у вас есть веб-форма, в которой некоторые поля не обязательны. На мой взгляд, лучше указать это особое «значение»enum
само по себе, чем использовать немного неуклюжееNullable<T>
. Это будет быстрее (без бокса), легче читать (Season.NotSpecified
vsnull
) и решит вашу проблему с хеш-кодами.Конечно, для других типов, например,
int
вы не можете расширить область значений, и обозначить одно из значений как особенное не всегда возможно. Но сint?
хэш-кодом проблема гораздо меньше, если вообще существует.источник
Nullable<>
структуры (гдеHasValue
затем будет установлен членtrue
). Вы уверены, что проблема действительно поменьшеint?
? Часто используется только несколько значенийint
, а затем это эквивалентно перечислению (которое теоретически может иметь много членов).int
имеет смысл. Конечно, предпочтения бывают разные.Tuple.Create( (object) null! ).GetHashCode() // 0 Tuple.Create( 0 ).GetHashCode() // 0 Tuple.Create( 1 ).GetHashCode() // 1 Tuple.Create( 2 ).GetHashCode() // 2
источник