应该什么时候.NET类覆盖等于()? 什么时候不应该?
VS2005文档重载等于()和操作符==(C#编程指南)的指南部分说明
不建议在非不可变类型中覆盖operator ==。
较新的.NET Framework 4文档实现等于和等式运算符(==)的指南省略了该语句,尽管社区内容中的一篇post重复了断言并引用了旧文档。
似乎至少对于一些琐碎的可变类来重写Equals()是合理的,例如
public class ImaginaryNumber { public double RealPart { get; set; } public double ImaginaryPart { get; set; } }
在数学中,具有相同实部和相同虚部的两个虚数实际上在测试相等性的时间点是相等的。 声明它们不相等是不正确的,如果具有相同RealPart和ImaginaryPart的单独对象未被覆盖Equals(),则会发生这种情况。
另一方面,如果一个覆盖Equals(),则还应覆盖GetHashCode()。 如果将覆盖Equals()和GetHashCode()的ImaginaryNumber放在HashSet中,并且可变实例更改其值,则不再在HashSet中找到该对象。
MSDN是否不正确删除有关不重写Equals()
和operator==
的非不可变类型的准则?
为可变类型重写Equals()是否合理,其中“在现实世界中”所有属性的等价意味着对象本身是相等的(与ImaginaryNumber
)?
如果它是合理的,当对象实例参与HashSet或依赖于GetHashCode()的其他东西没有改变时,如何最好地处理潜在的可变性?
UPDATE
刚刚在MSDN中遇到过这个问题
通常,当期望将类型的对象添加到某种类型的集合时,或者当它们的主要目的是存储一组字段或属性时,实现值相等。 您可以根据类型中所有字段和属性的比较来定义值相等,或者可以将定义基于子集。 但无论是哪种情况,还是在类和结构中,您的实现都应遵循等效的五个保证:
我开始意识到我希望Equals意味着两种不同的东西,具体取决于上下文。 在这里以及这里对输入进行权衡之后,我已根据我的具体情况确定了以下内容:
我没有重写Equals()
和GetHashCode()
,而是保留了常见的但并非无处不在的约定, Equals()
意味着类的身份相等,而Equals()
意味着结构的值相等。 如果我偏离此约定,则此决定的最大驱动因素是散列集合中的对象行为( Dictionary
, HashSet
,…)。
那个决定让我仍然错过了价值平等的概念(正如在MSDN上讨论的那样 )
定义类或结构时,您可以决定为类型创建值相等(或等效)的自定义定义是否有意义。 通常,当期望将类型的对象添加到某种类型的集合时,或者当它们的主要目的是存储一组字段或属性时,实现值相等。
希望价值平等概念(或者我称之为“等价”)的典型案例是unit testing。
特定
public class A { int P1 { get; set; } int P2 { get; set; } } [TestMethod()] public void ATest() { A expected = new A() {42, 99}; A actual = SomeMethodThatReturnsAnA(); Assert.AreEqual(expected, actual); }
测试将失败,因为Equals()
正在测试引用相等性。
unit testing肯定可以修改为单独测试每个属性,但是将等级的概念从类中移出到类的测试代码中。
为了将这些知识封装在类中,并为测试等价提供一致的框架,我定义了一个我的对象实现的接口
public interface IEquivalence { bool IsEquivalentTo(T other); }
实现通常遵循以下模式:
public bool IsEquivalentTo(A other) { if (object.ReferenceEquals(this, other)) return true; if (other == null) return false; bool baseEquivalent = base.IsEquivalentTo((SBase)other); return (baseEquivalent && this.P1 == other.P1 && this.P2 == other.P2); }
当然,如果我有足够的类具有足够的属性,我可以编写一个帮助器,通过reflection构建表达式树来实现IsEquivalentTo()
。
最后,我实现了一个扩展方法,测试两个IEnumerable
的等价:
static public bool IsEquivalentTo (this IEnumerable first, IEnumerable second)
如果T
实现IEquivalence
该接口,则使用Equals()
来比较序列的元素。 允许回Equals()
使得它除了我的业务对象之外还可以使用ObservableCollection
。
现在,我的unit testing中的断言是
Assert.IsTrue(expected.IsEquivalentTo(actual));
有关不重载的MSDN文档==
可变类型是错误的。 可变类型实现相等语义绝对没有错。 现在两个项目可以相等,即使它们将来会发生变化。
可变类型和相等性的危险通常出现在它们被用作哈希表中的键或允许可变成员参与GetHashCode
函数时。
查看Eric Lippert的 GetHashCode指南和规则 。
规则:当对象包含在依赖于哈希代码保持稳定的数据结构中时,GetHashCode返回的整数必须永远不会更改
虽然很危险,但允许一个对象的哈希码值可以随着对象的字段变异而变异。
我不了解您对 HashSet
GetHashCode
问题。GetHashCode
只返回一个帮助HashSet
内部存储和查找值的数字。如果对象的哈希码发生更改,则对象不会从 HashSet
删除,它将不会存储在最佳位置。
编辑
感谢@Erik JI看到了这一点。
HashSet
是一个性能集合,为了实现该性能,它完全依赖于GetHashCode
在集合的生命周期中保持不变。 如果您想要这种性能,那么您需要遵循这些规则。 如果你不能,那么你将不得不切换到List
类的东西