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