为什么在没有实现Equals()的情况下调用GetHashCode()不会产生警告?

4
对于标题的简略表述我表示抱歉,但实际标题会过长。以下是完整标题:

重写Equals() 而没有重写GetHashCode() 时为什么会有警告,而重写GetHashCode() 而没有重写Equals() 却不会有警告?

我预先道歉,这不是一个 “X是什么”类型的问题,更像是“我发现了X,我是否遗漏了些东西?”这类问题。
因此,请考虑以下内容:
class A
{
    public override bool Equals( object other ) => true; //warning CS0659
}

class B
{
    public override int GetHashCode() => 42; //No warning
}

我知道警告CS0659的原因是“类重写了Object.Equals(object o),但没有重写Object.GetHashCode()”。我不同意这些理由,因为Equals()对于可变类来说是完全可行的,而这种类当然不能用作哈希映射中的键,因此必须没有GetHashCode(),但我的反驳无关紧要,并且在任何情况下,接收到你不需要的警告永远不是问题,因为您可以始终将其禁用。
然而,当您可以考虑到需要的警告但未收到该警告时,就会出现问题。
上面的类B演示了这个问题。覆盖GetHashCode()而没有同时覆写Equals()几乎肯定是一个严重的错误,但却没有任何警告。既不是C#编译器,也不是ReSharper。
有人知道其中原因吗?
我有什么漏洞吗?
可能需要一些配置窍门才能从编译器那里得到警告吗?
我必须以某种奇怪的方式声明我的类,例如public sealed albino之类的事情吗?
缺少警告的正当理由吗?
编辑:
到目前为止,已经有了一些答案,它们基本上都陈述了同样的事情:没有警告,因为没有违反GetHashCode()Equals()的契约。
女士们先生们,你们错过了要点。
(或者我应该说是我道歉没有让这一点更清楚。)
支持缺少此警告的“无违反契约”论点与试图支持其他警告的缺席(例如“事件从未使用”)基本相同。基于声明事件并从未使用它不违反任何协议的原因来支持缺少此类警告,并没有多大意义。
首先,如果我通过声明事件并从未使用它来违反某些契约,那么我期望得到一个错误,而不是一个警告。
但是最重要的是,通过声明事件并从未使用它是一个严重而隐蔽的错误,所以需要一个警告,无论是否存在合同
实际上,确实有一个警告!
它是CS0067 “事件'{0}'从未使用”。
看到吗?警告不需要违反任何契约。
因此,该论点已经被打破了。
还有什么其他论点吗?

8
GetHashCode 方法返回一个常量而不重写 Equals 方法实际上是功能上正确的,因为它不会为相等的实例返回不同的哈希码。但是这样做会导致基于哈希表的集合的查找性能下降到 O(n),效率非常低下。 - György Kőszeg
4
“几乎可以肯定是一个严重的错误” - 然而,你的例子仍然遵守合同。 - Charles Mager
2
我认为这是因为GetHashCode必须依赖于Equals,而不是反过来。因此,当您更改Equals逻辑时,还应该更改GetHashCode,但是只要GetHashCode仍然遵循Equals规则,就可以更改GetHashCode实现而不更改Equals逻辑。 - Magnetron
2
如果有人没有理解“42”的参考,请查看https://www.dictionary.com/e/slang/42/。 - Charlieface
啊,好想念那些Eric Lippert亲自出现并回答这类问题的日子啊... (-:= - Mike Nakis
2个回答

5

GetHashCode() 方法应该反映 Equals 逻辑,规则如下:

  • 如果两个对象相等(Equals(...) == true),那么它们的 GetHashCode() 返回值必须相同。
  • 如果 GetHashCode() 值相等,它们不一定相同;这是一种碰撞,会调用 Equals 方法来确定是否真正相等。

谢谢,我已经知道这些,但这并没有回答我的问题。当覆盖Equals()而不覆盖GetHashCode()时,警告会提示可能违反合同,而合同可能是有意遵守的;当覆盖GetHashCode()而不覆盖Equals()时,警告会提示非常可能违反合同,而该合同肯定是要遵守的。(除非一个类打算作为哈希映射中的键,否则它不会实现GetHashCode()。) - Mike Nakis

2
当你仅覆盖GetHashCode()方法时,没有任何东西被破坏或违反(在Equals()/GetHashCode()合同方面)。Equals()方法仍然返回true,如果它是同一个对象,并且对于每个其他对象都将返回false。当你仅覆盖GetHashCode()方法时,没有办法违反合同。
  • 两个指向完全相同的对象的引用将返回相同的GetHashCode(),因为它实际上是同一个对象,并且对象的状态在此期间没有更改,正如GetHashCode()的合同所要求的那样。(Equals() => 相同的GetHashCode()

  • 此外,当两个哈希码不同时,这意味着这两个对象必须不相等。但由于两个不同的对象在没有覆盖时从来不会相等,因此此要求自动得到满足。(不同的GetHashCode() => 不Equals()

由于您没有违反任何合同,因此编译器不会发出任何警告(与您提到的warning CS0659不同)。在这种情况下,不需要覆盖Equals()方法。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接