Как реализовать 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() для пустой строки, а не сразу возвращает известную константу, но даже без явного измерения, я желаю рискнуть предположить, что разница не будет достаточно большой, чтобы беспокоиться, если вы не ожидаете много нулей.

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

  2. Если вы можете делать предположения об использовании вашего класса, вы можете улучшить свою хэш-функцию. Например, если обычно 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 должен быть:

  1. Быстро настолько, насколько это возможно.
  2. Как можно более уникальным.

Имея это в виду, я бы пошел с чем-то вроде этого:

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

Другие вопросы по тегам