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

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

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

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

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

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

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

Я проверил несколько GetHashCode() вопросы здесь, а также MSDN и статья Эрика Липперта по этой теме, но не смогли найти то, что я ищу.

4 ответов


Я думаю, что ваше решение-это хорошо. (Гораздо позже замечание: для 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.


код из ответа 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;
}

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


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

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

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

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

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

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

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


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

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