【问题标题】:Overloading operator== versus Equals()重载 operator== 与 Equals()
【发布时间】:2010-12-18 11:58:37
【问题描述】:

我正在开发一个 C# 项目,到目前为止,我一直在使用不可变对象和工厂来确保 Foo 类型的对象始终可以与 == 进行比较是否相等。

Foo 对象一旦创建就无法更改,并且工厂始终为给定的一组参数返回相同的对象。这很好用,并且在整个代码库中,我们假设== 始终用于检查相等性。

现在我需要添加一些引入边缘情况的功能,但这种情况并不总是有效。最简单的做法是为该类型重载operator ==,这样项目中的其他代码都不需要更改。但这让我觉得代码味道很重:重载operator == 而不是Equals 看起来很奇怪,而且我习惯了== 检查引用相等性和Equals 检查对象相等性(或任何术语)的约定是)。

这是一个合理的担忧,还是我应该继续超载operator ==

【问题讨论】:

  • 顺便说一句,vb.net 禁止将其=<> 相等运算符用于不提供显式重载的类型;要检查引用相等性,可以使用IsIsNot,它们基本上总是 检查引用相等性(主要的例外是将可空类型与Nothing 进行比较时)。

标签: c# operator-overloading equals


【解决方案1】:

根据 Microsoft 自己的最佳实践,Equals 方法和等号 (==) 重载的结果应该相同。

CA2224: Override equals on overloading operator equals

【讨论】:

  • 在几乎所有重载静态绑定== 的情况下,还应该覆盖虚拟Equals 成员,但反之则不然。一个人应该只为 sealed 类型重载==,这些类型的行为非常像值(String 的方式),但对于非密封类型,应该经常覆盖Equals。请注意,许多程序员希望将== 运算符应用于除string 之外的任何类类型是检查引用相等性的简写。
【解决方案2】:

显示如何根据MSFT guidelines(下)实现此功能的示例。请注意,在覆盖 Equals 时,您还需要覆盖 GetHashCode()。希望对大家有所帮助。

public class Person
{
    public Guid Id { get; private set; }

    public Person(Guid id)
    {
        Id = id;
    }

    public Person()
    {
        Id = System.Guid.NewGuid();
    }

    public static bool operator ==(Person p1, Person p2)
    {
        bool rc;

        if (System.Object.ReferenceEquals(p1, p2))
        {
            rc = true;
        }
        else if (((object)p1 == null) || ((object)p2 == null))
        {
            rc = false;
        }
        else
        {
            rc = (p1.Id.CompareTo(p2.Id) == 0);
        }

        return rc;
    }

    public static bool operator !=(Person p1, Person p2)
    {
        return !(p1 == p2);
    }

    public override bool Equals(object obj)
    {
        bool rc = false;
        if (obj is Person)
        {
            Person p2 = obj as Person;
            rc = (this == p2);
        }
        return rc;
    }

    public override int GetHashCode()
    {
        return Id.GetHashCode();
    }
}

【讨论】:

  • 您的空检查有缺陷。如果 both 的人都为 null,它会在应该返回 true 时返回 false。您想在两个空检查之间使用异或而不是布尔 OR。
  • ReferenceEquals 如果 p1 和 p2 都为 null,则返回 true。因此,布尔 OR 代码永远不会执行,运算符将按预期返回 true。
  • 顺便说一句,如果 Person 可以被子类化,您的编码风格将不起作用。相反,将逻辑放在“Equals”方法中,并让运算符“==”调用 Object.Equals(a, b)。原因:调用哪个“==”是基于 COMPILE-TIME 类型而不是 DYNAMIC 类型。 Object.Equals 确实动态调度。如果 Person 是子类,还需要精确比较类型,而不仅仅是强制转换为 Person。 “Equals”的正确编码示例在这里。 msdn.microsoft.com/en-us/library/336aedhh(v=vs.85).aspx 观察“GetType() != obj.GetType())”。当存在子类时,这是必不可少的。
  • @ToolmakerSteve:但是让== 与所有其他运算符不同的行为也不是一个很好的设计......在将运算符与多态引用结合起来时需要小心。
【解决方案3】:

重载 ==覆盖 Equals 之间有很大区别。

当你有表情时

if (x == y) {

用于比较变量 x 和 y 的方法在编译时决定。这是运算符重载。声明 x 和 y 时使用的类型用于定义用于比较它们的方法。 x 和 y 中的实际类型(即子类或接口实现)是无关紧要的。请考虑以下内容。

object x = "hello";
object y = 'h' + "ello"; // ensure it's a different reference

if (x == y) { // evaluates to FALSE

还有以下

string x = "hello";
string y = 'h' + "ello"; // ensure it's a different reference

if (x == y) { // evaluates to TRUE

这表明用于声明变量 x 和 y 的类型用于确定使用哪种方法来评估 ==。

相比之下,Equals 是在运行时根据变量 x 中的实际类型确定的。 Equals 是 Object 上的一个虚方法,其他类型可以并且确实可以覆盖。因此,以下两个示例的计算结果都为真。

object x = "hello";
object y = 'h' + "ello"; // ensure it's a different reference

if (x.Equals(y)) { // evaluates to TRUE

还有以下

string x = "hello";
string y = 'h' + "ello"; // ensure it's a different reference

if (x.Equals(y)) { // also evaluates to TRUE

【讨论】:

  • 这是非常重要的一点,也是我后来在测试这个的时候遇到的。因为我需要operator== 来依赖运行时类型,所以我在名为Equals() 的基类中定义了一个运算符重载。像魅力一样工作。 (然后我重新制作了对象模型并将整个东西撕掉了。但那是另一回事。)
  • 我完全不同意。使用== 是一个常见的成语。仅仅因为 C# 将所有实体都基于object 并不意味着我们应该放弃该成语以防止本文中出现这种情况。相反,当需要在层次结构的顶部进行比较时,最好使用.Equals()
  • 简而言之,重载 == 设置调用哪个方法(Object.Equals 或 Object.ReferenceEqual)。重载 Object.Equals 以检查实际值是否相等。我说的对吗?
  • @Xaade,''Equals 的行为不同是非常危险的 IMO。它们应该被编程为做同样的事情,您可以提供IEqualityComparer 的各种实现以提供替代的同等实现。
  • 哇,这是 .NET 设计中的一个可怕的错误。仅仅因为我使用对象 x 而不是字符串 x 来定义我的变量,两个字符串就不相等的想法是一种奖励级代码气味。谢谢你解释得这么好。
【解决方案4】:

对于不可变类型,我认为重载== 以支持值相等没有什么问题。我认为我不会在不覆盖 Equals 的情况下覆盖 == 以获得相同的语义。如果您确实覆盖了==,并且由于某种原因需要检查引用相等性,您可以使用Object.ReferenceEquals(a,b)

看到这个Microsoft article for some useful guidelines

【讨论】:

  • 只有当不可变类型也是 SEALED 时才是一个好主意。如果该类型可以被子类化,那么最遗憾的是,调用哪个“==”是由 COMPILE-TIME 变量声明决定的,而不是由运行时的动态类型(可能是子类)决定的。
【解决方案5】:

我相信标准是,对于大多数类型,.Equals 检查对象相似性,运算符 == 检查引用相等性。

我认为最佳实践是对于不可变类型,运算符 ==.Equals 应该检查相似性。如果您想知道它们是否真的是同一个对象,请使用.ReferenceEquals。有关此示例,请参阅 C# String 类。

【讨论】:

  • 没有。引用类型的“标准”是两个 Equals en == 返回与 RefernceEquals 相同的值。不鼓励对非不可变类型进行重载。
  • 另外,请注意此处的空值。如果 x 为 null,x.Equals(null) 将抛出 NRE,而 null == x 将起作用。
  • “标准”是解决某些类型问题的常用方法,如果“标准”妨碍了,那么它就不是很好的标准。
  • 比尔,但这是一个非常好的标准。您想要a != b 但 Collection 类认为它们是重复的类型吗?
  • @Bill,我只是因为不遵守标准而有所收获。这就是为什么我要问这里是什么。这应该足以表明标准很重要。编码风格标准(旨在提高组内的可读性)和实用编码标准(避免很多麻烦)之间存在差异。
【解决方案6】:

确实有味道。当重载== 时,您应该确保Equals()GetHashCode() 也是一致的。请参阅MSDN guidelines

这看起来完全没问题的唯一原因是您将您的类型描述为不可变的。

【讨论】:

  • 我将属性与方法信息进行比较。这些在语义上是不可变的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-10-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-06-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多