SequenceEqual в Equals делает GetHashCode неработоспособным

(1) Я знаю, что GetHashCode должен возвращать одно и то же число для двух объектов, если они равны.

(2) Я также знаю, что SequenceEqual сравнивает каждое значение List, а Equals(list1, list2) возвращает значение true, только если list1 и list2 являются одним и тем же экземпляром.

Итак, рассмотрим этот код:

public List<ClassB> SampleList { get; set; }
public string Str { get; set; }
protected bool Equals(Uncorrectable other)
{
    return Enumerable.SequenceEqual(this.SampleList, other.SampleList) && string.Equals(this.Str, other.Str);
}

public override bool Equals(object obj)
{
    if (ReferenceEquals(null, obj)) { return false; }
    if (ReferenceEquals(this, obj)) { return true; }
    if (obj.GetType() != this.GetType()) { return false; }
    return this.Equals((ClassA) obj);
}

public override int GetHashCode()
{
    unchecked 
    { 
        return 
            ((this.SampleList != null ? this.SampleList.GetHashCode() : 0)*397) ^
            (this.Str != null ? this.Str.GetHashCode() : 0); 
    }
}

Мне действительно нужно это поведение ((2) с использованием SequenceEqual) для Equals, в основном для модульного тестирования: сделайте этот код Assert.AreEqual(classA1, classA2) работающим.

Но часть моего кода, вероятно, сломана, потому что в этом случае

int hash1 = new List<ClassB>().GetHashCode(); 
int hash2 = new List<ClassB>().GetHashCode(); 

hash1 и hash2 не равны.

Так что в моем ClassA (1) не соблюдается.

Какое лучшее решение:

  1. Измените метод ClassA.Equals, чтобы использовать Equals(this.SampleList, other.SampleList) вместо Enumerable.SequenceEqual(this.SampleList, other.SampleList), и измените все мои тесты.
  2. Создайте еще одну реализацию IEnumerable, где переопределение Equals похоже на SequenceEqual.
  3. Измените ClassA.GetHashCode, чтобы вызывать GetHashCode для всех элементов списка.
  4. Ничего не делать
  5. Еще один?

person ZwoRmi    schedule 19.02.2016    source источник


Ответы (1)


Просто не основывайте свой GetHashCode на SampleList: вам не нужно использовать все поля/свойства в GetHashCode().

Например:

unchecked 
{ 
    return 
        (this.Str != null ? this.Str.GetHashCode() : 0); 
}

или даже лучше использовать только некоторую информацию SampleList... Count например:

unchecked 
{ 
    return 
        ((this.SampleList != null ? this.SampleList.Count.GetHashCode() : 0) * 397) ^
        (this.Str != null ? this.Str.GetHashCode() : 0); 
}

Если вы действительно хотите, вы можете вычислить GetHashCode() по элементам SampleList.

Теперь о турнире по обфускации кода C#, выпуск 2016 года:

unchecked
{
    return
        (397 * (this.SampleList != null ? 
            this.SampleList.Aggregate(0, (old, curr) => 
                (old * 397) ^ (curr != null ? curr.GetHashCode() : 0)) : 
            0)
        ) ^
        (this.Str != null ? this.Str.GetHashCode() : 0);
} 

(пожалуйста, не пишите так... Используйте цикл foreach)

person xanatos    schedule 19.02.2016
comment
Теперь это так очевидно (Где-то в моем мозгу была идея, что обратное значение (1) должно быть истинным). Если ClassA не предназначен для включения в большой список (у меня будет только 10 другой экземпляр ClassA), и что производительность мне на самом деле не нужна, согласны ли мы, что я могу использовать второе решение (используя длину или какое-то другое поле)? - person ZwoRmi; 19.02.2016
comment
@ZwoRmi Да. Обратите внимание, что вычисление хэша большого SampleList в общем случае может быть очень долгим... Так, например, вы можете даже схитрить и включить в хеш только его фиксированное подмножество... (this.SampleList.Count * 397) ^ this.SampleList.Take(5).Aggregate - person xanatos; 19.02.2016
comment
Если вы просто полностью проигнорируете список, частота конфликтов, вероятно, будет очень высокой, что приведет к очень низкой производительности всего, что использует хеш-код. - person Servy; 19.02.2016
comment
@Servy Все зависит от количества других полей. Ясно, что если SampleList является частью объекта, то игнорировать его очень плохо. Если это всего лишь одна из многих частей, то даже игнорирование не вызовет особых проблем. - person xanatos; 19.02.2016