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