C# 重载相等运算符时,为什么要重写GetHashCode和Equals?
重载相等运算符时未能重写C# 重载相等运算符时,为什么要重写GetHashCode和Equals?,c#,.net,operator-overloading,C#,.net,Operator Overloading,重载相等运算符时未能重写GetHashCode和Equals,会导致编译器产生警告。为什么改变这两种方法的实施都是一个好主意?阅读后,似乎没有太多有用的替代GetHashCode的基本实现的方法,为什么编译器我鼓励您更改它?如果您没有重载Equals方法,那么使用它可能会得到与使用运算符得到的结果不同的结果。比如,如果您为整数重载= int i = 1; (1 == 1) == (i.Equals(1)) 可能评估为错误 出于同样的原因,您应该重新实现GetHashCode方法,这样就不会弄乱
GetHashCode
和Equals
,会导致编译器产生警告。为什么改变这两种方法的实施都是一个好主意?阅读后,似乎没有太多有用的替代GetHashCode的基本实现的方法,为什么编译器我鼓励您更改它?如果您没有重载Equals
方法,那么使用它可能会得到与使用运算符得到的结果不同的结果。比如,如果您为整数重载=
int i = 1;
(1 == 1) == (i.Equals(1))
可能评估为错误
出于同样的原因,您应该重新实现GetHashCode
方法,这样就不会弄乱哈希表和其他依赖于哈希比较的结构
注意,我说的是“可能”和“可能”,而不是“意志”。这些警告只是提醒你,如果你不听从它的建议,可能会发生意想不到的事情。否则,您将得到错误而不是警告。非常清楚这一点:
GetHashCode方法可以由派生类型重写。价值
类型必须重写此方法以提供
适用于该类型,并在
哈希表。为了唯一性,哈希代码必须基于值
用于实例字段或属性而不是静态字段或属性
财产
在哈希表对象中用作键的对象也必须重写
GetHashCode方法,因为这些对象必须生成自己的哈希
代码。如果用作键的对象未提供有用的
实现GetHashCode时,可以指定哈希代码提供程序
当构建哈希表对象时。在.NET框架之前
版本2.0时,哈希代码提供程序基于
System.Collections.IHashCodeProvider接口。从版本开始
哈希代码提供程序基于System.Collections.IEqualityComparer接口
我的猜测是,编译器从您的操作中获得了线索,并决定,既然您发现提供等式运算符的替代实现很重要,那么您可能希望对象等式与
=
的新实现保持一致。毕竟,您不希望这两个相等比较意味着截然不同的事情,否则您的程序即使在非常基本的层面上也很难理解。因此,编译器认为您也应该重新定义等于
但是,一旦提供了替代实现
Equals
,就需要修改GetHashCode
,以与equality实现保持一致。因此,编译器警告您,您的实现可能不完整,并建议重写Equals
和GetHashCode
如果您在重写=
时不重写Equals()
,您将得到一些非常糟糕的代码
你对这件事有什么感觉
if (x == y)
{
if (!x.Equals(y))
throw new InvalidOperationException("Wut?");
}
这里有一个例子。鉴于这一类别:
class Test
{
public int Value;
public string Name;
public static bool operator==(Test lhs, Test rhs)
{
if (ReferenceEquals(lhs, rhs))
return true;
if (ReferenceEquals(lhs, null) || ReferenceEquals(rhs, null))
return false;
return lhs.Value == rhs.Value;
}
public static bool operator!=(Test lhs, Test rhs)
{
return !(lhs == rhs);
}
}
此代码的行为将异常:
Test test1 = new Test { Value = 1, Name = "1" };
Test test2 = new Test { Value = 1, Name = "2" };
if (test1 == test2)
Console.WriteLine("test1 == test2"); // This gets printed.
else
Console.WriteLine("test1 != test2");
if (test1.Equals(test2))
Console.WriteLine("test1.Equals(test2)");
else
Console.WriteLine("NOT test1.Equals(test2)"); // This gets printed!
你不想要这个让我们假设您正在实现一个类 如果重载
==
,则生成的类型具有值相等而不是引用相等
考虑到这一点,现在的问题是“有一个类在.Equals()
中实现引用相等,在==
中实现值相等有多理想?”答案是“不太理想”。这似乎是一个潜在的混乱来源。(事实上,我现在为Coverity工作的公司生产了一个缺陷发现工具,可以检查您是否正因为这个原因而混淆了值相等和引用相等。巧合的是,当我看到您的问题时,我正在阅读规范!)
此外,如果您将拥有一个同时实现值和引用相等的类,那么通常的方法是重写Equals
,并将==
单独保留,而不是相反
因此,考虑到您已经重载了=
,强烈建议您也重写Equals
如果您要重写
Equals
以产生值相等,那么您需要重写GetHashCode
以进行匹配,如果您已经阅读了链接到的我的文章,您就会知道。编译器不鼓励您。框架设计者也没有。他们只是给你选择权。请随意使用默认实现。@DavidHeffernan生成警告的编译器在我看来无疑是“鼓励”。@Servy这是一种不正确的阅读方式。编译器的意思是,如果必须这样做,请正确执行。@DavidHeffernan如果您重写=
运算符,编译器将鼓励您重写等于和GetHashCode
。这就是OP所说的。显然,它并不总是鼓励你在任何情况下这样做;OP没有断言它有。OP声称它鼓励你在做X时这样做,并问为什么。说“它不鼓励你”是错误的。大卫·费弗南:我认为你在第一次答复中说的话有点误导人。有些人可能会认为它的意思是“你可以忽略编译器警告”,我很确定你不是这个意思。如果你收到那个警告,你不应该忽略它。事实上…那个代码是有效的…尽管很奇怪。不过,它确实是一个非常有效的例子,说明了引用和值相等之间的区别。X和Y都可以是值为1的整数,但它们不是同一个对象……虽然对于整数,我认为.Equals应该是真的,但这不是重点。这取决于你想检查什么我想…如果你想检查它们是否是eq