定义运算符 == 但不定义 Equals() 或 GetHashCode() 有什么问题
本文关键字:定义 什么 问题 GetHashCode 运算符 Equals | 更新日期: 2023-09-27 18:35:38
对于下面的代码
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
的性能问题。
一般来说,覆盖一个(但不是全部)是令人困惑的。 用户不希望使用相同的语义覆盖两者。
Microsoft对此状态的建议(除其他事项外):
每当实现 Equals 方法时,都要实现 GetHashCode 方法。这使 Equals 和 GetHashCode 保持同步。
每当实现相等运算符 (==) 时,重写 Equals 方法,并使它们执行相同的操作。
在您的情况下,您有正当理由遵从Equals
(编译器不会自动实现==
)并仅覆盖这两个(==
/!=
)。 但是,仍然存在性能问题,因为ValueType.Equals
使用反射:
"重写特定类型的 Equals 方法以改进 该方法的性能和更贴近的概念 类型相等。
因此,仍然建议最后覆盖所有(==
/!=
/Equals
)。 当然,对于这个微不足道的结构来说,性能可能并不重要。
框架内普遍期望某些操作应始终产生相同的结果。原因是某些操作(特别是排序和搜索,它们构成了任何应用程序的很大一部分)依赖于这些不同的操作,从而产生有意义且一致的结果。在这种情况下,您打破了其中几个假设:
- 如果在
a
和b
之间有一个有效的运算==
,它应该产生与a.Equals(b)
相同的结果 - 类似地,如果在
a
和b
之间有一个有效的操作!=
,它应该产生与!a.Equals(b)
相同的结果 - 如果存在两个
a
和b
的对象,a == b
,则a
和b
在存储在哈希表中时应产生相同的键。
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"));
}
这是哈希表的一个基本属性,如果突然不为真,会导致非常奇怪的问题。
我猜你会收到这些警告,因为编译器不知道你在==
方法中使用了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<Person, Person> dict = new Dictionary<Person, Person>();
dict.Add(p1, p1);
var x1 = dict[p2]; //Since p2 is supposed to be equal to p1 (according to `==`), this should return p1
但这会抛出一个异常,比如KeyNotFound
但是如果你添加
public override bool Equals(object obj)
{
return Math.Abs(ID - ((Person)obj).ID) <= 5;
}
public override int GetHashCode()
{
return 0;
}
你会得到你想要的。
编译器只是警告您可能会面临类似的情况
需要做的就是在您的结构中添加另一个成员,例如 Forename。
因此,如果您有两个ID为63但名字不同的人,他们是否相等?
一切都取决于您要实现的"相同"定义。
使用更好的示例结构,编写一个 noddy 应用程序来执行各种方法,看看当你改变相等和/或等价的定义时会发生什么,如果它们不是全部步调一致,你最终会得到类似 !(a == b) != (a != b),这可能是真的,但是如果你不覆盖所有使用你代码的方法,就会想知道你的意图是什么。
基本上,编译器告诉你要做一个好公民,并明确你的意图。
可能是因为默认的 Equals()
方法对于实际系统来说不够好(例如,在您的类中,它应该比较ID
字段)。
阅读 MSDN 页面。
CS0660
CS0661
编译器基本上是在说:"既然你说知道如何比较你的对象,你应该让它一直这样比较。
如果你覆盖Equals
和GetHashCode
你甚至不需要覆盖运算符,这是一种更干净的方法。已编辑:它应该可以工作,因为这是一个结构。
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;
}
}
下面是一个示例。希望对您有所帮助。