Как вы реализуете GetHashCode для структуры с двумя строками, когда обе строки взаимозаменяемы

StackOverflow https://stackoverflow.com/questions/70303

  •  09-06-2019
  •  | 
  •  

Вопрос

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top