C# .NET类何时应重写Equals()?什么时候不应该?
VS2005文档部分说明了 不建议在非不可变类型中重写运算符== 较新的.NETFramework4文档省略了该语句,尽管社区内容中的一篇文章重复了该断言并引用了较旧的文档 至少对于一些普通的可变类来说,重写Equals()似乎是合理的,例如C# .NET类何时应重写Equals()?什么时候不应该?,c#,equals,gethashcode,C#,Equals,Gethashcode,VS2005文档部分说明了 不建议在非不可变类型中重写运算符== 较新的.NETFramework4文档省略了该语句,尽管社区内容中的一篇文章重复了该断言并引用了较旧的文档 至少对于一些普通的可变类来说,重写Equals()似乎是合理的,例如 public class ImaginaryNumber { public double RealPart { get; set; } public double ImaginaryPart { get; set; } } 在数学中,两个
public class ImaginaryNumber
{
public double RealPart { get; set; }
public double ImaginaryPart { get; set; }
}
在数学中,两个具有相同实部和相同虚部的虚数在检验相等性的时间点实际上相等。断言它们不相等是不正确的,如果具有相同RealPart和ImaginaryPart的单独对象不被重写,则会发生这种情况
另一方面,如果重写Equals(),则还应该重写GetHashCode()。如果将覆盖Equals()和GetHashCode()的ImaginaryNumber放置在HashSet中,并且可变实例更改了其值,则在HashSet中将不再找到该对象
MSDN删除了关于不重写非不可变类型的Equals()
和operator==
的指导原则,这是否不正确
如果“在现实世界中”所有属性的等价性意味着对象本身是相等的(如ImaginaryNumber
),那么为可变类型重写Equals()是否合理
如果这是合理的,那么当对象实例参与HashSet或其他依赖于GetHashCode()不变的对象时,如何最好地处理潜在的可变性
更新
刚刚遇到这个
通常,当类型的对象为
期望被添加到某种集合中,或者
主要目的是存储一组字段或属性。你可以
基于对所有价值相等的比较来定义价值相等
类型中的字段和属性,也可以基于
子集。但是在任何一种情况下,在类和结构中,您的
实施应遵循等效性的五个保证:
关于不为可变类型重载的MSDN文档是错误的。可变类型实现平等语义绝对没有错。两个项目现在可以相等,即使它们将来会改变 当可变类型和相等被用作哈希表中的键或允许可变成员参与
GetHashCode
函数时,通常会出现围绕可变类型和相等的危险 我不理解您对GetHashCode
与HashSet
有关的顾虑GetHashCode
只返回一个有助于HashSet
内部存储和查找值的数字。如果对象的哈希代码发生更改,则对象不会从HashSet
中删除,它不会存储在最佳位置
编辑
多亏了@Erik J,我明白了这一点
HashSet
是一个性能集合,要实现该性能,它完全依赖于GetHashCode
在集合的生命周期内保持不变。如果你想要这样的表现,那么你需要遵循这些规则。如果您无法,则必须切换到类似于列表的其他内容
查看by
规则:当对象包含在依赖于哈希代码保持稳定的数据结构中时,GetHashCode返回的整数不得更改
虽然危险,但允许创建一个哈希代码值可能随着对象字段的变化而变化的对象
我逐渐意识到,我想让Equals代表两种不同的含义,这取决于上下文。在权衡了此处的输入以及之后,我针对我的特殊情况确定了以下几点: 我并没有重写
Equals()
和GetHashCode()
,而是保留了一个常见但并非无处不在的约定,即Equals()
意味着类的标识相等,而Equals()
意味着结构的值相等。这个决定的最大驱动因素是散列集合中对象的行为(Dictionary
,HashSet
,…),如果我偏离了这个约定
这一决定让我仍然缺少价值平等的概念(as)
当您定义一个类或结构时,您决定它是否有意义
为创建值相等(或等价)的自定义定义
类型。通常,当
类型应添加到某种类型的集合中,或者
它们的主要用途是存储一组字段或属性
在单元测试中,需要值相等(或者我称之为“等价”)概念的一个典型案例
给定
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()
正在测试引用相等
单元测试当然可以修改为单独测试每个属性,但这将等价性的概念从类转移到类的测试代码中
为了将这些知识封装在类中,并为测试等价性提供一致的框架,我定义了一个对象实现的接口
public interface IEquivalence<T>
{
bool IsEquivalentTo(T other);
}
当然,如果我有足够多的类和足够多的属性,我就可以编写一个助手,通过反射构建表达式树来实现IsEquivalentTo()
最后,我实现了一个扩展方法来测试两个IEnumerable
的等价性:
我认为,
ImaginaryNumber
是可变类型是不合理的。实际上,您可能应该将ImaginaryNumber实现为不可变值类型(struct)。int
,long
,double
等等。。。它们是不可变的。5
是一个5
-您不能
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);
}
static public bool IsEquivalentTo<T>
(this IEnumerable<T> first, IEnumerable<T> second)
Assert.IsTrue(expected.IsEquivalentTo(actual));