为什么在重载相等运算符时期望覆盖GetHashCode和Equals?

重载Equals运算符时未能覆盖GetHashCodeEquals会导致编译器产生警告。 为什么改变其中任何一个的实现是个好主意? 在阅读了Eric Lippert关于GetHashCode的博客文章之后,似乎可能没有很多有用的替代GetHashCode的基础实现,为什么编译器我鼓励你改变它?

我们假设你正在实现一个类。

如果你正在重载==那么你正在生成一个具有值相等而不是引用相等的类型。

鉴于此,现在的问题是“在== .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以保持与相等实现的一致性。 因此编译器会警告您实现可能不完整,并建议覆盖EqualsGetHashCode

如果你没有重载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接口。