Как вы реализуете 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 для этой структуры?
Решение
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') были одинаковыми (или все равные пары, если уж на то пошло).
Правило "как можно быстрее" не полностью соблюдается в этом решении, потому что в случае nulls это выполняет 'GetHashCode()' для пустой строки, а не немедленно возвращает известную константу, но даже без явного измерения я готов рискнуть предположить, что разница не будет достаточно большой, чтобы беспокоиться, если вы не ожидаете большого количества нулей.
Как правило, простой способ сгенерировать хэш-код для класса - это выполнить XOR для всех полей данных, которые могут участвовать в генерации хэш-кода (тщательно проверяя наличие null, как указывали другие).Это также соответствует (искусственному?) требованию, чтобы хэш-коды для userInfo ("AA", "BB") и userInfo ("BB", "AA") были одинаковыми.
Если вы можете делать предположения об использовании вашего класса, возможно, вы сможете улучшить свою хэш-функцию.Например, если str1 и str2 обычно совпадают, XOR может оказаться неподходящим выбором.Но если str1 и str2 представляют, скажем, имя и фамилию, XOR, вероятно, является хорошим выбором.
Хотя это явно не должно быть примером из реального мира, возможно, стоит отметить, что:- Вероятно, это плохой пример использования структуры:Структура обычно должна иметь семантику значения, чего, похоже, здесь нет.- Использование свойств с установщиками для генерации хэш-кода также приводит к возникновению проблем.
Простой Общая информация способ состоит в том, чтобы сделать это:
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);
}
}
Следуя тем линиям, которые предлагает 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 str1.GetHashCode() + str2.GetHashCode();
Может переполниться.Вы могли бы попробовать использовать значение long, как предложил Артем, или вы могли бы заключить оператор в ключевое слово unchecked:
return unchecked(str1.GetHashCode() + str2.GetHashCode());
Попробуйте вот это:
(((long)str1.GetHashCode()) + ((long)str2.GetHashCode())).GetHashCode()
Много возможностей.Например.
return str1.GetHashCode() ^ str1.GetHashCode()
Возможно, что-то вроде str1.GetHashCode() + str2.GetHashCode()?или (str1.GetHashCode() + str2.GetHashCode()) / 2?Таким образом, это было бы одно и то же независимо от того, поменяны ли местами str1 и str2....
Отсортируйте их, затем объедините:
return ((str1.CompareTo(str2) < 1) ? str1 + str2 : str2 + 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 вряд ли будет распространен в необычно большом количестве экземпляров.