.NET 类何时应重写 Equals()?什么时候不应该?

2024-01-07

VS2005文档重载 Equals() 和运算符 == 的指南(C# 编程指南) http://msdn.microsoft.com/en-us/library/ms173147%28v=vs.80%29.aspx部分状态

不建议在非不可变类型中重写运算符 ==。

较新的 .NET Framework 4 文档实现等于和等于运算符 (==) 的指南 http://msdn.microsoft.com/en-us/library/7h9bszxx%28v=vs.100%29.aspx尽管社区内容中的一篇文章重复了该断言并引用了旧文档,但省略了该声明。

看来,至少对于一些简单的可变类,重写 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() and operator==对于非不可变类型?

对于可变类型重写 Equals() 是否合理,其中“在现实世界中”所有属性的等效性意味着对象本身是相等的(与ImaginaryNumber)?

如果合理的话,当对象实例参与 HashSet 或依赖 GetHashCode() 不改变的其他内容时,如何最好地处理潜在的可变性?

UPDATE

刚刚遇到这个in MSDN http://msdn.microsoft.com/en-us/library/dd183755.aspx

通常,当该类型的对象是 期望被添加到某种集合中,或者当它们 主要目的是存储一组字段或属性。你可以 您对价值平等的定义基于对所有 类型中的字段和属性,或者您可以将定义基于 子集。但无论哪种情况,在类和结构中,您的 实施应遵循五项等效保证:


我开始意识到,根据上下文,我希望“等于”具有两种不同的含义。在权衡此处的输入以及here https://stackoverflow.com/questions/9707918/simplify-overriding-equals-gethashcode-in-c-sharp-for-better-maintainabilit,针对我的具体情况,我决定如下:

我没有凌驾于一切之上Equals() and GetHashCode(),而是保留共同但绝不是普遍存在的惯例Equals()意味着类的身份平等,并且Equals()意味着结构体的值相等。这一决定的最大驱动因素是散列集合中对象的行为(Dictionary<T,U>, HashSet<T>,...)如果我偏离了这个惯例。

这个决定让我仍然错过了价值平等的概念(如MSDN 上讨论过 http://msdn.microsoft.com/en-us/library/dd183755.aspx)

当你定义一个类或结构时,你决定它是否有意义 创建值相等(或等价)的自定义定义 方式。通常,当以下对象时,您可以实现值相等 类型预计会被添加到某种集合中,或者当 它们的主要目的是存储一组字段或属性。

渴望价值平等(或者我称之为“等价”)概念的一个典型案例是在单元测试中。

Given

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);
}

实现通常遵循以下模式:

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);
}

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

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

static public bool IsEquivalentTo<T>
    (this IEnumerable<T> first, IEnumerable<T> second)

If T实施IEquivalence<T>使用该接口,否则Equals()用于比较序列的元素。允许回退到Equals()让它工作,例如和ObservableCollection<string>除了我的业务对象。

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

Assert.IsTrue(expected.IsEquivalentTo(actual));
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

.NET 类何时应重写 Equals()?什么时候不应该? 的相关文章

随机推荐

  • 使用 LINQ 过滤集合

    假设我们有一个 Person 对象的集合 class Person public string PersonName get set public string PersonAddress get set 以及代码定义集合中的某处 List
  • 从命令行使用 git 属性

    我的仓库中有这样的配置 echo java diff java gt gt gitattributes 当我这样做时git diff 我得到了预期的结果 不过 我正在编写一个脚本来分析其他 git 存储库 所以我通常会这样做 git git
  • Vaadin 会话管理 - 它是如何工作的?

    在工作中 我们使用 Vaadin 开发一个 Web 应用程序 我是一位相当高级的 Java 程序员 我对 Vaadin 也有经验 但现在我已经到了需要将信息存储在用户会话中的地步 区域设置 用户名等属性 In the Vaadin 文档 h
  • Django Auth LDAP - 使用 sAMAccountName 直接绑定

    有两种方法可以使用 Django Auth LDAP 对用户进行身份验证 搜索 绑定和 直接绑定 第一个涉及匿名或使用固定帐户连接到 LDAP 服务器并搜索身份验证用户的可分辨名称 然后我们可以尝试再次使用用户的密码进行绑定 第二种方法是从
  • 自动判断用户定义函数是否与隐式函数等价

    有时 用户实现的函数具有与其隐式定义版本相同的功能 例如 复制构造函数只调用其所有成员的复制构造函数 struct A int B A const A a B a B 这是不希望的 因为它会导致额外的维护 例如 如果类成员被重命名 重新排序
  • $.clone 和 .cloneNode

    我对 jQuery 之间的区别有点困惑 clone和原始的 cloneNode财产 如果我在做 blah cloneNode true 这将在 jQuery 空间之外创建一个全局对象 If I use blah clone true 这将在
  • 允许 foreach 工作人员注册并将子任务分配给其他工作人员

    我有一个 R 代码 其中涉及多个 foreach 工作人员来并行执行一些任务 我正在使用 foreach 和 doMC 来实现此目的 我想让每个 foreach 工作人员招募一些新工作人员 并将其代码的某些部分 可并行 分发给他们 当前的代
  • 增强 now.js/socket.io 聊天的安全性

    与聊天nowjs http nowjs org or 套接字 io http socket io 是您可以用它们进行的最简单的练习之一 我想使用 nowjs 的 Group 对象实现多房间聊天 具有非固定数量的房间和登录用户 我还没有直接使
  • 从内存中的 ASCII 而不是从文件连接反序列化对象

    实际问题 如何将对象序列化为 ASCII 并再次从 ASCII 反序列化without必须对文件连接进行写入和读取 即从内存中的 ASCII 背景 在无状态的客户端 服务器框架中 我想使某些信息在调用中持久化 序列化 gt gt 发送到客户
  • 无法从 Func 转换为 Func

    我对这个错误很困惑 Cannot implicitly convert type System Func
  • jQuery - 在单个事件处理程序中组合选择器的问题

    这是关于 Patrick DW 对我对这个问题的回答的评论 多个选择器 确定触发选择器 https stackoverflow com questions 4315075 multiple selectors identify the tr
  • PHP include_once

    使用 PHP 效率更高吗include once or require once而不是使用类似 C 的include带头护罩 I e include once init php versus include init php content
  • 如何将 YouTube 句柄映射到频道 ID

    YouTube 最近推出handles https www youtube com handle他们为用户提供了 youtube com xxx 类型的用户名 当访问这些 URL 时会显示用户的频道 但我在 API 存储库中找不到任何文档或
  • 在 ASP.NET Core 中,IoC ASP 启动类是否解决了托管可扩展性框架通过目录和容器解决的问题?

    我读过这篇文章 MEF 托管可扩展性框架 与 IoC DI https stackoverflow com questions 108116 mef managed extensibility framework vs ioc di但它已经
  • centos中前台运行mysql

    我正在为 mysql 数据库构建 docker 镜像 为此 我必须在前台运行 mysql 而不是作为守护服务运行 我正在使用 centos 基础镜像 如何让mysql在前台运行 如果你跑mysqld safe CMD mysqld safe
  • 如何检查 $_GET 参数是否存在但没有值?

    我想检查是否app参数存在于 URL 中 但没有值 Example my url php app I tried isset and empty 但不起作用 我以前见过它是怎么做的 但我忘记了 空是正确的 你想同时使用 is set 和 e
  • 在 hg 状态中显示重命名?

    我知道 Mercurial 可以跟踪文件的重命名 但我如何才能跟踪文件的重命名show当我这样做时 我会重命名而不是添加 删除hg status 例如 而不是 A bin extract csv column pl A bin find m
  • 如何在 matlab 中读取具有可变十六进制值列的文本文件?

    我有一个相当大的文本文件 超过 16 000 行 其格式如下 ID Line Num Var Col Length Values HEX 45 00001 FFFF FFFF 0000 0000 45 00002 0000 0000 FFF
  • 回形针:一个模型中存在多个“has_attached_file”

    我的模型片段 attr accessible package1 file name package2 file name has attached file package1 has attached file package2 来自我的
  • .NET 类何时应重写 Equals()?什么时候不应该?

    VS2005文档重载 Equals 和运算符 的指南 C 编程指南 http msdn microsoft com en us library ms173147 28v vs 80 29 aspx部分状态 不建议在非不可变类型中重写运算符