Реализовать GetHashCode() для объектов, содержащих коллекции

Рассмотрим следующие объекты:

class Route
{
   public int Origin {get; set;}
   public int Destination {get; set;}
}

Маршрут реализует операторы равенства.

class Routing
{
   public List<Route> Paths {get; set;}
}

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

public override int GetHashCode()
        {
            return (Paths != null 
                        ? (Paths
                            .Select(p => p.GetHashCode())
                            .Sum()) 
                        : 0);
        }

Я проверил несколько вопросов GetHashCode() здесь, а также статью msdn и Eric Lippert по этой теме, но не смог найти то, что я ищу.

Ответы

Ответ 1

Я думаю, ваше решение в порядке. (Более позднее замечание: метод LINQ Sum будет действовать в контексте checked, поэтому вы можете очень легко получить OverflowException, а это значит, что это не так уж и хорошо.) Но обычным образом делать XOR (дополнение без переноса). Так что это может быть что-то вроде

public override int GetHashCode()
{
  int hc = 0;
  if (Paths != null)
    foreach (var p in Paths)
      hc ^= p.GetHashCode();
  return hc;
}

Добавление (после ответа было принято):

Помните, что если вы когда-либо использовали этот тип Routing в Dictionary<Routing, Whatever>, a HashSet<Routing> или другую ситуацию, когда используется хеш-таблица, ваш экземпляр будет потерян, если кто-то изменит (мутирует) Routing после того, как он был добавлен в коллекцию.

Если вы уверены, что этого не произойдет, используйте мой код выше. Dictionary<,> и т.д. все равно будут работать, если вы убедитесь, что никто не изменяет ссылку Routing, на которую ссылаются.

Другой выбор - просто написать

public override int GetHashCode()
{
  return 0;
}

если вы считаете, что хеш-код никогда не будет использоваться. Если каждый instace возвращает 0 для хэш-кода, вы получите очень плохую производительность с хэш-таблицами, но ваш объект не будет потерян. Третий вариант - выбросить NotSupportedException.

Ответ 2

Код от Jeppe Stig Nielsen отвечает, но это может привести к многому повторению значений хеш-кода. Скажем, вы хешируете список ints в диапазоне 0-100, тогда ваш хэш-код будет гарантированно находиться в диапазоне от 0 до 255. Это приводит к большому количеству конфликтов при использовании в словаре. Вот улучшенная версия:

public override int GetHashCode()
{
  int hc = 0;
  if (Paths != null)
    foreach (var p in Paths) {
        hc ^= p.GetHashCode();
        hc = (hc << 7) | (hc >> (32 - 7)); //rotale hc to the left to swipe over all bits
    }
  return hc;
}

Этот код будет по крайней мере включать все биты в течение времени, поскольку все больше и больше элементов хэшируются.

Ответ 3

В качестве ориентира хэш объекта должен быть одинаковым по всему объекту. Я оставил бы функцию GetHashCode один, а не перезаписывал ее. Хэш-код используется, только если вы хотите поместить свои объекты в хеш-таблицу.

Вы должны прочитать статью Эрика Липперта о хэш-кодах в .NET: Рекомендации и правила для GetHashCode.

Цитата из этой статьи:

Guideline: целое число, возвращаемое GetHashCode, никогда не должно меняться

Правило: целое число, возвращаемое GetHashCode, никогда не должно меняться, пока объект содержится в структуре данных, которая зависит от оставшегося хеш-кода

Если хэш-код объекта может мутировать, когда он находится в хеш-таблице, то, очевидно, метод Contains перестает работать. Вы помещаете объект в ведро # 5, вы его мутируете, и когда вы задаете вопрос, содержит ли он мутированный объект, он выглядит в ведре # 74 и не находит его.

Функция GetHashCode, которую вы внедрили, не вернет тот же хэш-код в течение всего жизненного цикла объекта. Если вы используете эту функцию, у вас возникнут проблемы, если вы добавите эти объекты в хеш-таблицу: метод Contains не будет работать.

Ответ 4

Я не думаю, что это правильный способ, потому что для завершения окончательного hashcode он должен быть уникальным для указанного объекта. В вашем случае вы делаете Sum(), который может выдавать тот же результат с разными хэш-кодами в коллекции (в конце хэш-коды являются целыми).

Если вы намерены определить равенство, основанное на содержании коллекции, на этом этапе просто сравните эти промежутки между двумя объектами. Между прочим, это может быть трудоемкая операция.