V3229. The 'GetHashCode' method may return different hash codes for equal objects. It uses an object reference to generate a hash for a variable. Check the implementation of the 'Equals' method.
Анализатор обнаружил, что результат GetHashCode зависит от хэш-кода ссылки на некоторый объект. При этом результат Equals зависит от логики сравнения этого объекта, отличной от сравнения двух ссылок. Это может привести к нарушению контракта: два одинаковых объекта согласно методу Equals должны также иметь одинаковые хэш-коды.
Нарушение контракта методов GetHashCode и Equals может привести к некорректной работе некоторых функций и структур данных:
- коллекции вроде
HashSetиDictionary; - LINQ-методы, такие как
Union,Intersectи пр.
Рассмотрим пример:
public HashSet<Value> Values { get; private set; } = new();
....
public override bool Equals(object obj)
{
if (obj is not CustomObject other)
return false;
return Values.SequenceEqual(other.Values);
}
public override int GetHashCode()
{
return Values.GetHashCode();
}
В качестве возвращаемого значения GetHashCode некоторого класса используется хэш-код от ссылки на коллекцию Values. При этом в методе Equals происходит поэлементное сравнение с этой коллекцией с помощью SequenceEqual.
Способ N1. Упростить сравнение объектов:
public HashSet<Value> Values { get; private set; } = new();
....
public override bool Equals(object obj)
{
if (obj is not CustomObject other)
return false;
return Values.Equals(other.Values);
}
public override int GetHashCode()
{
return Values.GetHashCode();
}
Способ N2. Алгоритм в GetHashCode также должен учитывать хэши объектов коллекции:
public readonly HashSet<Value> Values { get; private set; } = new();
....
public override bool Equals(object obj)
{
if (obj is not CustomObject other)
return false;
return Values.SequenceEqual(other.Values);
}
public override int GetHashCode()
{
return Values.Aggregate(0,
(hash, val) => (hash * 397)
^ (val?.GetHashCode() ?? 0));
}
Вызов Aggregate формирует общий хэш-код элементов коллекции путём их перебора и обновления значения hash на каждой итерации согласно функции, переданной вторым аргументом.
Примечание. Необходимо также учитывать, что к методу GetHashCode предъявляются требования по скорости работы, а способ N2 может нарушить их.