未能覆盖GetHashCode
和Equals
重载相等运算符时会导致编译器产生警告。为什么改变两者的实现是个好主意?在阅读了 Eric Lippert 关于 GetHashCode 的博文后,似乎没有很多有用的替代方法可以替代 GetHashCode 的基本实现,为什么我鼓励您更改编译器?
5 回答
假设您正在实现一个类。
如果您正在重载==
,那么您将生成一个具有值相等而不是引用相等的类型。
.Equals()
鉴于此,现在的问题是“拥有一个在 中实现引用相等和值相等的类有多可取==
?” 答案是“不太理想”。这似乎是一个潜在的混乱来源。(事实上,我现在工作的公司 Coverity 生产了一个缺陷发现工具,用于检查您是否正是出于这个原因将值相等与引用相等混淆了。巧合的是,当我看到它时,我正在阅读它的规范你的问题!)
此外,如果您要拥有一个实现值和引用相等的类,通常的方法是覆盖并不Equals
理会==
,而不是相反。
因此,鉴于您已经重载==
,强烈建议您也覆盖Equals
。
如果您要重写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
。
但是,一旦提供了替代实现Equals
,您就需要进行修改GetHashCode
以与相等实现保持一致。因此,编译器会警告您您的实现可能不完整,并建议同时覆盖Equals
和GetHashCode
。
如果您也没有重载该Equals
方法,那么使用它可能会产生与使用运算符获得的结果不同的结果。就像,如果你重载=
整数......
int i = 1;
(1 == 1) == (i.Equals(1))
可以评估为假。
出于同样的原因,您应该重新实现该GetHashCode
方法,这样您就不会弄乱哈希表和其他依赖哈希比较的结构。
请注意,我说的是“可能”和“可能”,而不是“将”。这些警告只是提醒您,如果您不遵循它的建议,可能会发生意想不到的事情。否则你会得到错误而不是警告。
文档对此非常清楚:
GetHashCode 方法可以被派生类型覆盖。值类型必须重写此方法以提供适合该类型的散列函数并在散列表中提供有用的分布。为了唯一性,哈希码必须基于实例字段或属性的值,而不是静态字段或属性。
用作 Hashtable 对象中的键的对象也必须覆盖 GetHashCode 方法,因为这些对象必须生成自己的哈希码。如果用作键的对象未提供有用的 GetHashCode 实现,则可以在构造 Hashtable 对象时指定哈希码提供程序。在 .NET Framework 2.0 版之前,哈希码提供程序基于 System.Collections.IHashCodeProvider 接口。从 2.0 版开始,哈希码提供程序基于 System.Collections.IEqualityComparer 接口。