Как реализовать GetHashCode для структуры с двумя строками, когда обе строки являются взаимозаменяемыми - PullRequest
66 голосов
/ 16 сентября 2008

У меня есть структура в 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 для этой структуры?

Ответы [ 14 ]

65 голосов
/ 16 сентября 2008

* MSDN 1002 *:

Хеш-функция должна иметь следующие свойства:

  • Если два объекта сравниваются как равные, метод GetHashCode для каждого объекта должен возвращать одинаковое значение. Однако, если два объекта не сравниваются как равные, методы GetHashCode для двух объектов не должны возвращать разные значения.
  • Метод GetHashCode для объекта должен последовательно возвращать один и тот же хэш-код, если нет изменения состояния объекта, определяющего возвращаемое значение метода Equals объекта. Обратите внимание, что это верно только для текущего выполнения приложения, и что при повторном запуске приложения может быть возвращен другой хэш-код.
  • Для лучшей производительности хеш-функция должна генерировать случайное распределение для всех входных данных.

С учётом правильного пути это:

return str1.GetHashCode() ^ str2.GetHashCode() 

^ можно заменить другой коммутативной операцией

25 голосов
/ 22 июня 2009

См. Ответ Джона Скита - двоичные операции, такие как ^, не годятся, они часто генерируют коллизионный хэш!

15 голосов
/ 16 сентября 2008
public override int GetHashCode()
{
    unchecked
    {
        return (str1 ?? String.Empty).GetHashCode() +
            (str2 ?? String.Empty).GetHashCode();
    }
}

Использование оператора «+» может быть лучше, чем использование «^», поскольку, хотя вы явно хотите, чтобы («AA», «BB») и («BB», «AA») явно были одинаковыми, вы можете не хочу, чтобы ('AA', 'AA') и ('BB', 'BB') были одинаковыми (или все равные пары в этом отношении).

Правило «как можно быстрее» не полностью соблюдается в этом решении, потому что в случае нулей это выполняет GetHashCode () для пустой строки, а не сразу возвращает известную константу, но даже без явного измерения Я готов рискнуть предположить, что разница не будет достаточно большой, чтобы беспокоиться, если вы не ожидаете много нулей.

5 голосов
/ 16 сентября 2008
  1. Как правило, простой способ генерации хеш-кода для класса - это XOR для всех полей данных, которые могут участвовать в генерации хеш-кода (с осторожностью проверять нулевое значение, как указано другими). Это также соответствует (искусственному?) Требованию, чтобы хэш-коды для UserInfo («AA», «BB») и UserInfo («BB», «AA») были одинаковыми.

  2. Если вы можете делать предположения об использовании вашего класса, вы, возможно, можете улучшить свою хэш-функцию. Например, если для str1 и str2 характерно совпадение, XOR может быть не лучшим выбором. Но если str1 и str2 представляют, скажем, имя и фамилию, XOR, вероятно, является хорошим выбором.

Хотя это явно не означает, что это пример из реальной жизни, стоит отметить, что: - Это, вероятно, плохой пример использования структуры: структура обычно должна иметь семантику значений, чего здесь, похоже, нет. Использование свойств с установщиками для генерации хеш-кода также вызывает проблемы.

4 голосов
/ 08 мая 2014

Простой общий способ сделать это:

return string.Format("{0}/{1}", str1, str2).GetHashCode();

Если у вас нет строгих требований к производительности, это самый простой способ, о котором я могу думать, и я часто использую этот метод, когда мне нужен составной ключ. Он прекрасно обрабатывает null случаи и не вызовет (m) каких-либо коллизий хешей (в общем). Если вы ожидаете '/' в своих строках, просто выберите другой разделитель, который вы не ожидаете.

3 голосов
/ 06 февраля 2014

Продолжая в том же духе, 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 нет ничего особенного, что отличает его от других простых чисел той же величины.

3 голосов
/ 16 сентября 2008
public override int GetHashCode()   
{       
    unchecked      
    {           
        return(str1 != null ? str1.GetHashCode() : 0) ^ (str2 != null ? str2.GetHashCode() : 0);       
    }   
}
2 голосов
/ 16 сентября 2008

Ах да, как отметил Гэри Шатлер:

return str1.GetHashCode() + str2.GetHashCode();

Может переполниться. Вы можете попытаться привести к тому, что предложил Артем, или заключить выражение в ключевое слово unchecked:

return unchecked(str1.GetHashCode() + str2.GetHashCode());
1 голос
/ 16 сентября 2008

Попробуйте это:

(((long)str1.GetHashCode()) + ((long)str2.GetHashCode())).GetHashCode()
0 голосов
/ 16 сентября 2008

Слишком сложно, и забывает нули, и т. Д. Это используется для таких вещей, как группирование, поэтому вы можете получить что-то вроде

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 вряд ли будет распространен в необычно большом количестве случаев.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...