Как реализовать GetHashCode для структуры с двумя строками, когда обе строки взаимозаменяемы
У меня есть структура в С#:
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 для этой структуры?
Ответы
Ответ 1
MSDN:
Функция хеширования должна иметь следующие свойства:
- Если два объекта сравниваются как равные, метод
GetHashCode
для каждого объекта должен возвращать одно и то же значение. Однако, если два объекта не сравниваются как равные, методы GetHashCode
для двух объектов не должны возвращать разные значения. - Метод
GetHashCode
для объекта должен последовательно возвращать один и тот же хэш-код, если не будет изменений в состоянии объекта, которое определяет возвращаемое значение метода Equals
объекта. Обратите внимание, что это верно только для текущего выполнения приложения и что другой хэш-код может быть возвращен, если приложение снова запущено. - Для лучшей производительности хеш-функция должна генерировать случайное распределение для всех входных данных.
Принимая во внимание правильный способ:
return str1.GetHashCode() ^ str2.GetHashCode()
^
можно заменить другой коммутативной операцией
Ответ 2
Смотрите ответ Jon Skeet - двоичные операции, такие как ^
, не очень хорошие, они часто генерируют встречный хеш!
Ответ 3
public override int GetHashCode()
{
unchecked
{
return (str1 ?? String.Empty).GetHashCode() +
(str2 ?? String.Empty).GetHashCode();
}
}
Использование оператора "+" может быть лучше, чем использование "^", потому что, хотя вы явно хотите ( "AA", "BB" ) и ( "BB", "AA" ) явно быть одинаковыми, вы можете ( "AA", "AA" ) и ( "BB", "BB" ) должны быть одинаковыми (или все равные пары в этом отношении).
Правило "как можно быстрее" не полностью соблюдается в этом решении, потому что в случае нулей он выполняет "GetHashCode()" на пустой строке, а не сразу возвращает известную константу, но даже без явного измерения Я готов рискнуть предположить, что разница не будет достаточно большой, чтобы беспокоиться, если вы не ожидаете большого количества нулей.
Ответ 4
-
Как правило, простой способ генерации хэш-кода для класса - это XOR все поля данных, которые могут участвовать в генерации хеш-кода (при этом нужно следить за значением null, как указано другими). Это также соответствует требованию (искусственное?), Чтобы хэш-коды для UserInfo ( "AA", "BB" ) и UserInfo ( "BB", "AA" ) были одинаковыми.
-
Если вы можете сделать предположения об использовании своего класса, вы можете улучшить свою хеш-функцию. Например, если для str1 и str2 является общим, то XOR может не быть хорошим выбором. Но если str1 и str2 представляют, скажем, имя и фамилию, XOR, вероятно, хороший выбор.
Несмотря на то, что это явно не предназначено для примера в реальном мире, может быть, стоит отметить, что:
- Вероятно, это плохой пример использования структуры: у структуры обычно должна быть семантика значений, которая здесь, похоже, не имеет места.
- Использование свойств с сеттерами для генерации хэш-кода также вызывает проблемы.
Ответ 5
public override int GetHashCode()
{
unchecked
{
return(str1 != null ? str1.GetHashCode() : 0) ^ (str2 != null ? str2.GetHashCode() : 0);
}
}
Ответ 6
Переходя по строкам, 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, которое отличает его от других простых чисел одинаковой величины.
Ответ 7
Ах да, как указал Гэри Шаттер:
return str1.GetHashCode() + str2.GetHashCode();
Может переполняться. Вы можете попробовать кастинг до тех пор, как предложил Артем, или вы могли бы окружить утверждение в непроверенном ключевом слове:
return unchecked(str1.GetHashCode() + str2.GetHashCode());
Ответ 8
Простым общим способом является следующее:
return string.Format("{0}/{1}", str1, str2).GetHashCode();
Если у вас нет строгих требований к производительности, это самый легкий, о котором я могу думать, и часто использую этот метод, когда мне нужен составной ключ. Он отлично обрабатывает случаи null
и не вызывает (m) любых хеш-коллизий (в общем). Если вы ожидаете '/' в своих строках, просто выберите другой разделитель, которого вы не ожидаете.
Ответ 9
Попробуйте следующее:
(((long)str1.GetHashCode()) + ((long)str2.GetHashCode())).GetHashCode()
Ответ 10
Много возможностей. Например
return str1.GetHashCode() ^ str1.GetHashCode()
Ответ 11
Возможно, что-то вроде str1.GetHashCode() + str2.GetHashCode()? или (str1.GetHashCode() + str2.GetHashCode())/2? Таким образом, это будет одно и то же, независимо от того, заменены ли str1 и str2....
Ответ 12
Отсортируйте их, затем объедините их:
return ((str1.CompareTo(str2) < 1) ? str1 + str2 : str2 + str1)
.GetHashCode();
Ответ 13
Результат 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();
Изменить: Забыл null. Исправлен код.
Ответ 14
Слишком сложный и забывающий нули и т.д. Это используется для таких вещей, как bucketing, поэтому вы можете уйти с чем-то вроде
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 вряд ли будет распространяться в необычно большой доли экземпляров.