2016-02-19 6 views
0

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

(2) Я также знаю, что SequenceEqual сравнить каждое значение List и Equals(list1, list2) возвращает истину, только если 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. Еще один?

ответ

1

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

Например:

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

или даже лучше использовать только некоторую информацию о SampleList ... The 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 цикл)

+0

Так сейчас очевидно (где-то в мозг был идеей того, что обратное ** (1) ** должно быть истинным).Если ClassA не предназначен для большого списка (у меня будет только 10 различных экземпляров ClassA), и что мне действительно не нужна производительность, согласны ли мы с тем, что я могу использовать второе решение (используя длину или другое поле)? – ZwoRmi

+0

@ ZwoRmi Да. Обратите внимание, что вычисление хэша большого 'SampleList' может быть очень длинным в общем ... Так например, вы могли бы даже« обмануть »и включить в хэш только фиксированное подмножество этого ...' (this.SampleList.Count * 397)^this.SampleList.Take (5) .Aggregate' – xanatos

+0

Если вы просто полностью игнорируете список, вероятность столкновения, вероятно, будет * очень * высокой, что приведет к очень низкой производительности того, что использует хэш-код. – Servy