我已经阅读了大约 10 个关于何时以及如何重写 GetHashCode
的不同问题,但仍有一些我不太明白。 GetHashCode
的大多数实现都基于对象字段的哈希码,但有人指出,GetHashCode
的值在对象的整个生命周期内都不应更改。如果它所基于的字段是可变的,那将如何工作?另外,如果我确实希望字典查找等基于引用相等性而不是我覆盖的 Equals
怎么办?
我主要覆盖 Equals
以便于对我的序列化代码进行单元测试,我假设序列化和反序列化(在我的情况下为 XML)会破坏引用相等性,所以我想至少确保值(value)平等是正确的。在这种情况下重写 Equals
是一种不好的做法吗?基本上在大多数执行代码中,我都希望引用相等,并且我总是使用 ==
并且我不会覆盖它。我应该只创建一个新方法 ValueEquals
还是其他方法而不是重写 Equals
?我曾经假设框架总是使用 ==
而不是 Equals
来比较事物,所以我认为覆盖 Equals
是安全的,因为它在我看来,它的目的是为了让你拥有不同于 ==
运算符的第二个相等性定义。从阅读其他几个问题来看,情况似乎并非如此。
编辑:
我的意图似乎并不明确,我的意思是 99% 的时间我想要普通的旧引用相等性、默认行为,没有意外。在极少数情况下,我想要值相等,并且我想通过使用 .Equals
而不是 ==
来明确请求值相等。
当我这样做时,编译器建议我也覆盖 GetHashCode
,这就是这个问题的来源。 GetHashCode
应用于可变对象时似乎存在相互矛盾的目标,这些目标是:
- 如果
a.Equals(b)
那么a.GetHashCode()
应该== b.GetHashCode()
。 a.GetHashCode()
的值在a
的生命周期内永远不会改变。
当一个可变对象时,这些看起来很自然地矛盾,因为如果对象的状态发生变化,我们期望 .Equals()
的值发生变化,这意味着 GetHashCode
应该更改以匹配 .Equals()
中的更改,但 GetHashCode
不应更改。
为什么会出现这种矛盾呢?这些建议是否不适用于可变对象?可能是假定的,但可能值得一提的是我指的是类而不是结构。
解决方案:
我将 JaredPar 标记为已接受,但主要用于评论交互。总结一下我从中学到的是,实现所有目标并避免在边缘情况下可能出现的古怪行为的唯一方法是仅重写基于 Equals
和 GetHashCode
在不可变字段上,或实现 IEquatable
。这种似乎削弱了为引用类型覆盖 Equals
的有用性,因为据我所知,大多数引用类型通常没有不可变字段,除非它们存储在关系数据库中以标识它们他们的主键。
最佳答案
如果它所基于的字段是可变的,它是如何工作的?
从某种意义上说,散列码不会随着对象的变化而变化。由于您阅读的文章中列出的所有原因,这都是一个问题。不幸的是,这种问题通常只出现在极端情况下。因此,开发人员往往会逃避不良行为。
另外,如果我确实希望字典查找等基于引用相等性而不是我重写的相等性怎么办?
只要你实现一个像IEquatable<T>
这样的接口(interface)这应该不是问题。大多数字典实现将以使用 IEquatable<T>
的方式选择相等比较器。在 Object.ReferenceEquals 之上。即使没有 IEquatable<T>
,大多数将默认调用 Object.Equals() 然后将进入您的实现。
基本上在大多数执行代码中我都希望引用相等并且我总是使用 == 并且我不会覆盖它。
如果您希望您的对象以值相等的方式运行,您应该覆盖 == 和 != 以强制所有比较的值相等。如果用户确实需要引用相等性,他们仍然可以使用 Object.ReferenceEquals。
我曾经假设框架总是使用 == 而不是 Equals 来比较事物
随着时间的推移,BCL 使用的内容发生了一些变化。现在大多数使用相等的情况将采用 IEqualityComparer<T>
实例并将其用于平等。在未指定的情况下,他们将使用 EqualityComparer<T>.Default
找到一个。在最坏的情况下,这将默认调用 Object.Equals
关于c# - 覆盖可变对象的 GetHashCode?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/873654/