定义operator ==但不定义Equals()或GetHashCode()有什么问题?

对于下面的代码

public struct Person { public int ID; public static bool operator ==(Person a, Person b) { return a.Equals(b); } public static bool operator !=(Person a, Person b) { return !a.Equals(b); } } 

为什么编译器会给我这些警告?
没有定义下面的方法有什么问题?

 warning CS0660: 'Person' defines operator == or operator != but does not override Object.Equals(object o) warning CS0661: 'Person' defines operator == or operator != but does not override Object.GetHashCode() 

编辑 :此答案已得到纠正,除其他事项外,请注意用户定义的值类型不生成== ,并提及ValueType.Equals的性能问题。


一般来说,覆盖一个,但不是全部,是令人困惑的。 用户希望不会被重写,或者两者都被覆盖,具有相同的语义。

微软对此州的建议 (除其他外):

  • 实现Equals方法时实现GetHashCode方法。 这使Equals和GetHashCode保持同步。

  • 每当实现相等运算符(==)时重写Equals方法,并使它们执行相同的操作。

在您的情况下,您有正当理由推迟到Equals (编译器不会自动实现== )并仅覆盖那两个( == / != )。 但是,由于ValueType.Equals使用reflection,因此仍存在性能问题:

“覆盖特定类型的Equals方法以提高方法的性能,并更接近地表示类型的相等概念。”

因此,仍然建议最后覆盖所有( == / != / Equals )。 当然,对于这个简单的结构,性能可能无关紧要。

我的猜测是你得到这些警告,因为编译器不知道你在==方法中使用Equals

假设您有此实现

 public struct Person { public int ID; public static bool operator ==(Person a, Person b) { return Math.Abs(a.ID - b.ID) <= 5; } public static bool operator !=(Person a, Person b) { return Math.Abs(a.ID - b.ID) > 5; } } 

然后

  Person p1 = new Person() { ID = 1 }; Person p2 = new Person() { ID = 4 }; bool b1 = p1 == p2; bool b2 = p1.Equals(p2); 

b1是真的 ,但是b2是假的

– 编辑 –

现在假设你想这样做

 Dictionary dict = new Dictionary(); dict.Add(p1, p1); var x1 = dict[p2]; //Since p2 is supposed to be equal to p1 (according to `==`), this should return p1 

但这会抛出类似KeyNotFound的exception

但是,如果你添加

 public override bool Equals(object obj) { return Math.Abs(ID - ((Person)obj).ID) <= 5; } public override int GetHashCode() { return 0; } 

你会得到你想要的。

编译器只是警告您可以面对类似的情况

框架内普遍期望某些操作应始终产生相同的结果。 原因是某些操作(特别是排序和搜索,构成任何应用程序的很大一部分)依赖于这些不同的操作,从而产生有意义且一致的结果。 在这种情况下,您打破了以下几个假设:

  • 如果ab之间存在有效的操作== ,则应该产生与a.Equals(b)相同的结果
  • 类似的,如果在ab之间存在有效的操作!= ,它应该产生与!a.Equals(b)相同的结果
  • 如果存在两个对象ab ,其中a == b ,则ab在存储在哈希表中时应生成相同的密钥。

IMO的前两个是显而易见的; 如果要定义两个对象相等的含义,则应该包括检查两个对象是否相等的所有方法。 请注意,编译器(通常不能 )强制您实际遵循这些规则。 它不会对运算符的主体执行复杂的代码分析,看看它们是否已经模仿Equals因为在最坏的情况下,这可能等同于解决暂停问题。

但它可以做的是检查你最有可能破坏这些规则的情况,特别是你提供了自定义比较运算符并且没有提供自定义的Equals方法。 这里的假设是,如果您不希望它们执行某些特殊操作,您就不会费心提供运算符,在这种情况下,您应该为所有需要同步的方法提供自定义行为。

如果你确实将Equals实现为与==不同的东西,那么编译器就不会抱怨; 你会达到C#愿意试图阻止你做一些愚蠢事情的极限。 它愿意阻止你不小心在你的代码中引入微妙的错误,但如果这就是你想要的,它会让你故意这样做。

第三个假设与框架中的许多内部操作使用哈希表的某些变体这一事实有关。 如果我有两个对象,根据我的定义,“相等”,那么我应该能够这样做:

 if (a == b) { var tbl = new HashTable(); tbl.Add(a, "Test"); var s = tbl[b]; Debug.Assert(s.Equals("Test")); } 

这是哈希表的基本属性,如果它突然不正确会导致非常奇怪的问题。

您需要做的就是在结构中添加另一个成员,例如Forename。

所以,如果你有两个身份证号为63但姓名不同的人,他们是否相同?

一切都取决于你想要实现的“相同”的定义。

使用一个更好的示例结构,编写一个虚拟的应用程序来执行各种方法,看看当你改变相等和/或等价的定义时会发生什么,如果它们不是一步到位,你最终会得到类似的东西!(a == b)!=(a!= b),这可能是真的,但如果你没有覆盖所有使用过你的方法代码,你会想知道你的意图是什么。

基本上编译器告诉你要成为好公民,并明确你的意图。

可能是因为默认的Equals()方法对于真实系统来说不够好(例如在你的类中它应该比较ID字段)。

阅读MSDN页面。

CS0660

CS0661

编译器基本上说:“既然你说知道如何比较你的对象,你应该让它一直比较那样。”

如果你重写EqualsGetHashCode你甚至不需要覆盖运算符,这是一种更清洁的方法。 编辑:它应该工作,因为这是一个结构。

 public struct Coord { public int x; public int y; public Coord(int x, int y) { this.x = x; this.y = y; } public static bool operator ==(Coord c1, Coord c2) { return c1.x == c2.x && c1.y == c2.y; } public static bool operator !=(Coord c1, Coord c2) { return !(c1 == c2); } public bool Equals(Coord other) { return x == other.x && y == other.y; } public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) return false; return obj is Coord && Equals((Coord) obj); } public override int GetHashCode() { return 0; } } 

这是一个例子。 希望它有用。