Как реализовать GetHashCode для структуры с двумя строками, когда обе строки являются взаимозаменяемыми
У меня есть структура в C#:
public struct UserInfo
{
public string str1
{
get;
set;
}
public string str2
{
get;
set;
}
}
Единственное правило заключается в том, что UserInfo(str1="AA", str2="BB").Equals(UserInfo(str1="BB", str2="AA"))
Как переопределить функцию GetHashCode для этой структуры?
15 ответов
MSDN:
Хеш-функция должна иметь следующие свойства:
- Если два объекта сравниваются как равные,
GetHashCode
Метод для каждого объекта должен возвращать одно и то же значение. Однако, если два объекта не сравниваются как равные,GetHashCode
методы для двух объектов не должны возвращать разные значения.GetHashCode
Метод для объекта должен последовательно возвращать один и тот же хэш-код, если нет изменения в состоянии объекта, которое определяет возвращаемое значение объекта.Equals
метод. Обратите внимание, что это верно только для текущего выполнения приложения, и что другой хэш-код может быть возвращен, если приложение будет запущено снова.- Для лучшей производительности хеш-функция должна генерировать случайное распределение для всех входных данных.
Принятие во внимание правильного способа это:
return str1.GetHashCode() ^ str2.GetHashCode()
^
может быть заменен другой коммутативной операцией
Смотрите ответ Джона Скита - бинарные операции, такие как ^
не являются хорошими, они часто генерируют хэш-код столкновения!
public override int GetHashCode()
{
unchecked
{
return (str1 ?? String.Empty).GetHashCode() +
(str2 ?? String.Empty).GetHashCode();
}
}
Использование оператора "+" может быть лучше, чем использование "^", поскольку, хотя вы явно хотите, чтобы ("AA", "BB") и ("BB", "AA") явно были одинаковыми, вы можете не захотеть ('AA', 'AA') и ('BB', 'BB') должны быть одинаковыми (или все равные пары в этом отношении).
Правило "как можно быстрее" не полностью соблюдается в этом решении, потому что в случае нулей это выполняет GetHashCode() для пустой строки, а не сразу возвращает известную константу, но даже без явного измерения, я желаю рискнуть предположить, что разница не будет достаточно большой, чтобы беспокоиться, если вы не ожидаете много нулей.
Как правило, простой способ генерации хеш-кода для класса - это XOR для всех полей данных, которые могут участвовать в генерации хеш-кода (тщательно проверяя наличие нуля, как указано другими). Это также соответствует (искусственному?) Требованию, чтобы хеш-коды для UserInfo("AA", "BB") и UserInfo("BB", "AA") были одинаковыми.
Если вы можете делать предположения об использовании вашего класса, вы можете улучшить свою хэш-функцию. Например, если обычно str1 и str2 совпадают, XOR может не быть хорошим выбором. Но если str1 и str2 представляют, скажем, имя и фамилию, XOR, вероятно, является хорошим выбором.
Хотя это явно не означает, что это пример из реальной жизни, стоит отметить, что: - Вероятно, это плохой пример использования структуры: структура обычно должна иметь семантику значений, которая, по-видимому, не является дело здесь. Использование свойств с установщиками для генерации хеш-кода также вызывает проблемы.
Продолжая в том же духе, ReSharper предлагает:
public int GetHashCode()
{
unchecked
{
int hashCode;
// String properties
hashCode = (hashCode * 397) ^ (str1!= null ? str1.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (str2!= null ? str1.GetHashCode() : 0);
// int properties
hashCode = (hashCode * 397) ^ intProperty;
return hashCode;
}
}
397 - простое число достаточного размера, чтобы вызвать переполнение результирующей переменной и несколько смешать биты хэша, обеспечивая лучшее распределение хэш-кодов. Иначе в 397 нет ничего особенного, что отличало бы его от других простых чисел той же величины.
Простой общий способ сделать это:
return string.Format("{0}/{1}", str1, str2).GetHashCode();
Если у вас нет строгих требований к производительности, я думаю, что это самый простой способ, и я часто использую этот метод, когда мне нужен составной ключ. Это обрабатывает null
просто отлично и не вызовет (m) каких-либо коллизий хешей (в общем). Если вы ожидаете '/' в ваших строках, просто выберите другой разделитель, который вы не ожидаете.
public override int GetHashCode()
{
unchecked
{
return(str1 != null ? str1.GetHashCode() : 0) ^ (str2 != null ? str2.GetHashCode() : 0);
}
}
Ах да, как отметил Гари Шутлер:
return str1.GetHashCode() + str2.GetHashCode();
Может переполниться. Вы можете попробовать выполнить приведение на long, как предложил Артем, или заключить выражение в ключевое слово unchecked:
return unchecked(str1.GetHashCode() + str2.GetHashCode());
Попробуйте это:
(((long)str1.GetHashCode()) + ((long)str2.GetHashCode())).GetHashCode()
Начиная с C# 7, мы можем использовать ValueTuple для этого:
return (str1, str2).GetHashCode();
Сортируйте их, затем объедините их:
return ((str1.CompareTo (str2)<1)? str1 + str2: str2 + str1).GetHashCode ();
Возможно, что-то вроде str1.GetHashCode() + str2.GetHashCode()? или (str1.GetHashCode() + str2.GetHashCode()) / 2? Таким образом, было бы то же самое, независимо от того, поменялись ли str1 и str2....
Много возможностей. Например
return str1.GetHashCode() ^ str1.GetHashCode()
Результат GetHashCode должен быть:
- Быстро настолько, насколько это возможно.
- Как можно более уникальным.
Имея это в виду, я бы пошел с чем-то вроде этого:
if (str1 == null)
if (str2 == null)
return 0;
else
return str2.GetHashCode();
else
if (str2 == null)
return str1.GetHashCode();
else
return ((ulong)str1.GetHashCode() | ((ulong)str2.GetHashCode() << 32)).GetHashCode();
Изменить: Забыли нули. Код исправлен.
Слишком сложный, и забывает нули, и т. Д. Это используется для таких вещей, как ведро, так что вы можете уйти с чем-то вроде
if (null != str1) {
return str1.GetHashCode();
}
if (null != str2) {
return str2.GetHashCode();
}
//Not sure what you would put here, some constant value will do
return 0;
Это смещено, если предположить, что str1 вряд ли будет распространен в необычно большом количестве случаев.