覆盖可变对象的GetHashCode?

Dav*_*vy8 57 .net c# overriding equals gethashcode

我已经阅读了关于何时以及如何覆盖的10个不同的问题,GetHashCode但仍然有一些我不太了解的问题.大多数实现GetHashCode都是基于对象字段的哈希码,但是引用的是值的值GetHashCode永远不会在对象的生命周期内发生变化.如果它所基于的字段是可变的,那该怎么办?另外,如果我确实希望字典查找等基于引用相等而不是我的重写Equals

我主要是Equals为了方便单元测试我的序列化代码,我假设序列化和反序列化(在我的情况下为XML)会导致引用相等,所以我想确保至少它的值是正确的.Equals在这种情况下,这种不良做法是否会被覆盖?基本上在大多数执行代码中,我希望引用相等,而且我总是使用,==而不是重写.我应该创建一个新的方法ValueEquals或什么而不是覆盖Equals?我曾经认为框架总是使用==而不是Equals比较事物,因此我认为覆盖是安全的,Equals因为在我看来,如果你想要有与==运算符不同的第二个等式定义,那么它的目的是什么.从阅读其他几个问题虽然看起来并非如此.

编辑:

看来我的意图不清楚,我的意思是99%的时候我想要普通的老参考平等,默认行为,没有惊喜.对于非常罕见的情况,我希望值相等,并且我希望通过使用.Equals而不是显式请求值相等==.

当我这样做时,编译器建议我也覆盖GetHashCode,这就是这个问题的出现方式.GetHashCode当应用于可变对象时,似乎存在矛盾的目标,即:

  1. 如果a.Equals(b)那时a.GetHashCode()应该== b.GetHashCode().
  2. a.GetHashCode()永远不应该改变的价值a.

当一个可变对象时,这些看起来自然是矛盾的,因为如果对象的状态发生变化,我们期望值的.Equals()变化,这意味着GetHashCode应该改变以匹配变化.Equals(),但GetHashCode不应该改变.

为什么会出现这种矛盾呢?这些建议不适用于可变对象吗?可能是假设,但可能值得一提的是我指的是不是结构的类.

解析度:

我将JaredPar标记为已被接受,但主要用于评论互动.总结我从中学到的是,在边缘情况下实现所有目标和避免可能的古怪行为的唯一方法是仅覆盖EqualsGetHashCode基于不可变字段或实现IEquatable.这种类似似乎削弱了覆盖Equals引用类型的有用性,因为从我看到的大多数引用类型通常没有不可变字段,除非它们存储在关系数据库中以使用它们的主键来标识它们.

Jar*_*Par 22

如果它所基于的字段是可变的,那该怎么办?

它并不意味着哈希代码会随着对象的变化而改变.这是您阅读的文章中列出的所有原因的问题.不幸的是,这种问题通常只出现在极端情况下.因此开发人员倾向于逃避不良行为.

如果我确实希望字典查找等基于引用相等而不是我重写的Equals呢?

只要你实现这样的接口IEquatable<T>应该不是问题.大多数字典实现将以将使用IEquatable<T>Object.ReferenceEquals 的方式选择相等比较器.即使没有IEquatable<T>,大多数都默认调用Object.Equals(),然后进入你的实现.

基本上在大多数执行代码中我想要引用相等而且我总是使用==而我并没有覆盖它.

如果您希望对象的行为具有值相等性,则应覆盖==和!=以强制执行所有比较的值相等.如果用户实际上想要引用相等,则仍然可以使用Object.ReferenceEquals.

我曾经假设框架总是使用==而不是Equals来比较事物

随着时间的推移,BCL使用的内容有所改变.现在大多数使用相等的情况都会使用一个IEqualityComparer<T>实例并将其用于相等.在没有指定一个的情况下,他们将使用它EqualityComparer<T>.Default来找到一个.在最坏的情况下,这将默认调用Object.Equals

  • @ Davy8,最好的方法是使用不可变字段.这样它就可以100%的时间工作.如果没有不可变字段,您可能需要重新考虑一下您的设计.要么使字段的子集不可变,要么不通过.Equals实现相等性.否则,您将只有一个主要工作的解决方案. (3认同)
  • "如果你希望你的对象表现为值相等,你应该覆盖==和!=来强制所有比较的值相等.如果用户实际上想要引用相等,用户仍然可以使用Object.ReferenceEquals." 但事情就是这样,我不希望它们表现为值相等,我希望它们在引用相等的情况下表现,除非我明确地希望它们不使用object.Equals,在其他所有情况下我都希望像大多数其他引用相同类. (2认同)

Guf*_*ffa 6

如果你有一个可变对象,那么覆盖GetHashCode方法没有太大意义,因为你无法真正使用它.它由例如DictionaryHashSet集合用于将每个项目放在一个桶中.如果在对象用作集合中的键时更改对象,则哈希码不再与对象所在的桶匹配,因此集合无法正常工作,您可能再也找不到该对象.

如果你想查找不使用GetHashCodeEquals之类的方法,可以随时为你自己IEqualityComparer,当你创建实现使用代替Dictionary.

Equals方法旨在实现值相等,因此以这种方式实现它并没有错.


sle*_*ske 5

哇,这实际上是几个问题合二为一:-)。于是一个接一个:

有人提到 GetHashCode 的值在对象的生命周期内永远不会改变。如果它所基于的字段是可变的,那如何工作?

此常见建议适用于您希望将对象用作 HashTable/dictionary 等中的键的情况。HashTables 通常要求不改变哈希值,因为它们使用它来决定如何存储和检索密钥。如果散列更改,HashTable 可能不再找到您的对象。

引用 Java 的Map接口的文档:

注意:如果将可变对象用作映射键,则必须非常小心。如果对象的值以影响相等比较的方式更改,而对象是映射中的键,则不会指定映射的行为。

一般来说,将任何类型的可变对象用作哈希表中的键都是一个坏主意:甚至不清楚如果将键添加到哈希表后会发生什么变化。哈希表应该通过旧键还是通过新键或两者都返回存储的对象?

所以真正的建议是:只使用不可变对象作为键,并确保它们的哈希码永远不会改变(如果对象是不可变的,这通常是自动的)。

另外,如果我确实希望字典查找等基于引用相等而不是我覆盖的 Equal 呢?

好吧,找到一个像这样工作的字典实现。但是标准库字典使用 hashcode&Equals,并且没有办法改变它。

我主要是覆盖 Equals 以便于对我的序列化代码进行单元测试,我假设序列化和反序列化(在我的情况下为 XML)会杀死引用相等,所以我想至少通过值相等来确保它是正确的。在这种情况下覆盖 Equals 是不好的做法吗?

不,我觉得这完全可以接受。但是,您不应将此类对象用作字典/哈希表中的键,因为它们是可变的。看上面。

  • 所以你的意思是,关于哈希码不改变的点并不那么重要,因为你不应该首先使用字典键作为可变对象? (2认同)