什么时候.NET类覆盖等于()?什么时候不应该?

Eri*_* J. 13 c# equals gethashcode

VS2005文档重载Equals()和Operator ==(C#编程指南)的指南部分说明

不建议在非不可变类型中覆盖operator ==.

较新的.NET Framework 4文档实现等于和等于运算符的指南(==)省略了该语句,尽管社区内容中的一篇帖子重复了断言并引用了旧文档.

似乎至少对于一些琐碎的可变类来重写Equals()是合理的,例如

public class ImaginaryNumber
{
    public double RealPart { get; set; }
    public double ImaginaryPart { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

在数学中,具有相同实部和相同虚部的两个虚数实际上在测试相等性的时间点是相等的.声明它们不相等是不正确的,如果具有相同RealPart和ImaginaryPart的单独对象未被覆盖Equals(),则会发生这种情况.

另一方面,如果一个重写Equals(),则还应该重写GetHashCode().如果将覆盖Equals()和GetHashCode()的ImaginaryNumber放在HashSet中,并且可变实例更改其值,则不再在HashSet中找到该对象.

MSDN是否不正确删除有关不覆盖Equals()operator==非不可变类型的指南?

为可变类型重写Equals()是否合理,其中"在现实世界中"所有属性的等价意味着对象本身是相等的(如同ImaginaryNumber)?

如果它是合理的,当对象实例参与HashSet或依赖于GetHashCode()的其他东西没有改变时,如何最好地处理潜在的可变性?

UPDATE

刚刚在MSDN中遇到过这个问题

通常,当期望将类型的对象添加到某种类型的集合时,或者当它们的主要目的是存储一组字段或属性时,实现值相等.您可以根据类型中所有字段和属性的比较来定义值相等,或者可以将定义基于子集.但无论是哪种情况,还是在类和结构中,您的实现都应遵循等效的五个保证:

Eri*_* J. 13

我开始意识到我希望Equals意味着两种不同的东西,具体取决于上下文.在这里以及此处对输入进行权衡之后,我已根据我的具体情况确定了以下内容:

我不是重写Equals()GetHashCode(),而是保留了共同但绝不是无处不在的约定Equals()意味着上课身份平等,这Equals()意味着对于结构值相等.这个决定的最大推动力是散列集合(对象的行为Dictionary<T,U>,HashSet<T>...)如果我从这个惯例流浪.

这个决定让我仍然错过了价值平等的概念(正如在MSDN上讨论的那样)

定义类或结构时,您可以决定是否为类型创建值相等(或等效)的自定义定义.通常,当期望将类型的对象添加到某种类型的集合时,或者当它们的主要目的是存储一组字段或属性时,实现值相等.

希望价值平等概念(或者我称之为"等价")的典型案例是在单元测试中.

特定

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);
}
Run Code Online (Sandbox Code Playgroud)

测试将失败,因为Equals()测试引用相等.

单元测试肯定可以被修改为单独测试每个属性,但是将等级的概念从类中移出到类的测试代码中.

为了保持封装在类中的知识,并为测试等价提供一致的框架,我定义了一个我的对象实现的接口

public interface IEquivalence<T>
{
    bool IsEquivalentTo(T other);
}
Run Code Online (Sandbox Code Playgroud)

实现通常遵循以下模式:

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);
}
Run Code Online (Sandbox Code Playgroud)

当然,如果我有足够的类具有足够的属性,我可以编写一个帮助器,通过反射来构建表达式树来实现IsEquivalentTo().

最后,我实现了一个测试两个等价的扩展方法IEnumerable<T>:

static public bool IsEquivalentTo<T>
    (this IEnumerable<T> first, IEnumerable<T> second)
Run Code Online (Sandbox Code Playgroud)

如果T器具IEquivalence<T>该接口被使用,否则Equals()使用时,比较该序列的元素.除了我的业务对象之外,允许回退Equals()使其工作ObservableCollection<string>.

现在,我的单元测试中的断言是

Assert.IsTrue(expected.IsEquivalentTo(actual));
Run Code Online (Sandbox Code Playgroud)

  • 我希望.NET定义了两组虚拟相等测试(或者包含一个参数来指示应该测试什么类型的相等性).让对象通过对对象实例进行可变类型引用来封装数据,该对象实例永远不会暴露给任何可能使其变异的对象,这是一种非常常见的模式; 引用所持有的对象应该提供在该场景中有意义的相等测试. (2认同)

Jar*_*Par 11

关于不==为可变类型重载的MSDN文档是错误的.可变类型实现相等语义绝对没有错.现在两个项目可以相等,即使它们将来会发生变化.

可变类型和相等性的危险通常出现在它们被用作哈希表中的键或允许可变成员参与该GetHashCode功能时.


Ale*_*kov 7

退房的GetHashCode的准则和规则埃里克利珀.

规则:当对象包含在依赖于哈希代码保持稳定的数据结构中时,GetHashCode返回的整数必须永远不会更改

虽然很危险,但允许一个对象的哈希码值可以随着对象的字段变异而变异.