【问题标题】:Overriding == operator. How to compare to null? [duplicate]覆盖 == 运算符。如何与空值进行比较? [复制]
【发布时间】:2011-05-12 06:43:59
【问题描述】:

可能重复:
How do I check for nulls in an ‘==’ operator overload without infinite recursion?

这可能有一个简单的答案......但它似乎在逃避我。这是一个简化的例子:

public class Person
{
   public string SocialSecurityNumber;
   public string FirstName;
   public string LastName;
}

假设对于这个特定的应用程序,如果社会安全号码匹配,并且两个名字匹配,那么我们指的是同一个“人”。

public override bool Equals(object Obj)
{
    Person other = (Person)Obj;
    return (this.SocialSecurityNumber == other.SocialSecurityNumber &&
        this.FirstName == other.FirstName &&
        this.LastName == other.LastName);
}

为了保持一致,我们也为团队中不使用 .Equals 方法的开发人员覆盖 == 和 != 运算符。

public static bool operator !=(Person person1, Person person2)
{
    return ! person1.Equals(person2);
}

public static bool operator ==(Person person1, Person person2)
{
    return person1.Equals(person2);
}

很好,花花公子,对吧?

但是,当 Person 对象为 null 时会发生什么?

你不能写:

if (person == null)
{
    //fail!
}

因为这将导致 == 运算符覆盖运行,并且代码将在以下位置失败:

person.Equals()

方法调用,因为您不能在空实例上调用方法。

另一方面,您不能在 == 覆盖中明确检查此条件,因为它会导致无限递归(以及 Stack Overflow [dot com])

public static bool operator ==(Person person1, Person person2)
{
    if (person1 == null)
    {
         //any code here never gets executed!  We first die a slow painful death.
    }
    return person1.Equals(person2);
}

那么,如何覆盖 == 和 != 运算符以实现值相等并仍然考虑空对象?

我希望答案不是简单得令人痛苦。 :-)

【问题讨论】:

  • SO 正在阻止对这个问题的进一步回答,但从 C# 7 开始,最好的解决方案是使用新的 is null 构造,即 if (person1 is null) ...
  • @Ross,无法添加答案,因为这是一个重复的问题。此外,如果您想指出原始问题的 is null 构造,您会发现其他人在您发表评论前 2 年已经回答了这个问题。
  • @gog 可能是这样,但这个问题在 Google 搜索中排名第一。似乎......令人难以置信的误导......让这个页面充满(现在)错误的答案没有得到纠正,希望人们会点击重复的链接,而不是使用这个页面上高度赞成和可行但过时的答案之一.如果 SO 的目标是提供帮助,则应尽一切努力完全删除此页面,或使其尽可能好。

标签: c# .net null overloading operator-keyword


【解决方案1】:

使用object.ReferenceEquals(person1, null) 或新的is operator 代替== 运算符:

public static bool operator ==(Person person1, Person person2)
{
    if (person1 is null)
    {
         return person2 is null;
    }

    return person1.Equals(person2);
}

【讨论】:

  • ReferenceEquals 会发出一个方法调用,而强制转换为对象将导致编译器仅发出指令来比较引用是否相等。 ...但这可能算作邪恶的微优化。
  • @dtb:JIT 无论如何都会内联这个调用;运行时性能将相同。见stackoverflow.com/questions/735554/…
  • 也许这很明显,但您可能还想对 Person.Equals 覆盖内的 Obj 参数进行空检查。在我看来,person == null 仍然会导致调用 person.Equals(null)
  • 是的,我依靠Equals 方法来检查空参数。我总是编写自己的 Equals 方法来考虑 null 情况,我希望其他人也这样做。 ;)
  • VS 2017 让我现在使用 is 运算符:left is null && right is null。
【解决方案2】:

我一直都是这样做的(对于 == 和 != 运算符),我为我创建的每个对象重复使用此代码:

public static bool operator ==(Person lhs, Person rhs)
{
    // If left hand side is null...
    if (System.Object.ReferenceEquals(lhs, null))
    {
        // ...and right hand side is null...
        if (System.Object.ReferenceEquals(rhs, null))
        {
            //...both are null and are Equal.
            return true;
        }

        // ...right hand side is not null, therefore not Equal.
        return false;
    }

    // Return true if the fields match:
    return lhs.Equals(rhs);
}

"!=" 然后是这样的:

public static bool operator !=(Person lhs, Person rhs)
{
    return !(lhs == rhs);
}

编辑
我修改了== 运算符函数以匹配微软建议的实现here

【讨论】:

  • 呃。很抱歉这么说,但这是代码膨胀的一个可怕例子。您需要 14 行来表达可以在带有条件表达式的单个 return 语句中表达的内容,就像可读性一样(为了便于阅读,分布在两到三行)。
  • @Konrad Rudolph - 那么,我该如何改进呢?你想到的两三行是什么?
  • 好吧,一方面,null 检查rhs 是多余的,因为Equals 也必须执行它。其余部分可以表示为return object.ReferenceEquals(lhs, rhs) || !object.ReferenceEquals(lhs, null) && lhs.Equals(rhs);——显然,使用适当的格式(换行符、缩进)使其可读。如果这太晦涩难懂,请在评论中添加相应 MSDN 文章的链接,该文章描述了相等运算符的语义要求。
  • 我对此表示赞成,因为它完全遵循了微软的建议,但也同意它可以在不失去可读性的情况下进行简化。示例:从 if (System.Object.ReferenceEquals(rhs, null)) 返回 false;可以替换为 return System.Object.ReferenceEquals(rhs, null);
【解决方案3】:

你总是可以覆盖并放置

(Object)(person1)==null

我想这会起作用,但不确定。

【讨论】:

  • 它有效……但它有点晦涩难懂(与使用 object.ReferenceEquals 相比)。
  • 这实际上非常有效,因为它相当于发出一条 CEQ 指令。 ReferenceEquals 方法调用了一个效率低得多的函数。
  • @W.KevinHazzard - 这不是真的;对 ReferenceEquals 的调用被优化为指针比较。
  • 你们俩讨论此优化所花费的时间比使用它所期望的要多。
【解决方案4】:

比任何这些方法都更容易使用

public static bool operator ==(Person person1, Person person2)   
{   
    EqualityComparer<Person>.Default.Equals(person1, person2)
} 

这与其他人提出的方法具有相同的空相等语义,但找出细节是框架的问题:)

【讨论】:

    【解决方案5】:

    最终的(假设的)例程如下。这与@cdhowie 第一次接受的回复非常相似。

    public static bool operator ==(Person person1, Person person2)
    {
        if (Person.ReferenceEquals(person1, person2)) return true;
        if (Person.ReferenceEquals(person1, null)) return false; //*
        return person1.Equals(person2);
    }
    

    感谢您的精彩回复!

    //* - .Equals() 对 person2 执行空检查

    【讨论】:

      【解决方案6】:

      Person 实例转换为object

      public static bool operator ==(Person person1, Person person2)
      {
          if ((object)person1 == (object)person2) return true;
          if ((object)person1 == null) return false;
          if ((object)person2 == null) return false;
          return person1.Equals(person2);
      }
      

      【讨论】:

        【解决方案7】:

        将 Person 转换为 Object,然后执行比较:

        object o1 = (object)person1;
        object o2 = (object)person2;
        if(o1==o2) //compare instances.
           return true;
        if (o1 == null || o2 == null)  //compare to null.
           return false;
        //continue with Person logic.
        

        【讨论】:

          【解决方案8】:

          始终重载这些运算符非常困难。 My answer to a related question 可以作为模板。

          基本上,你首先需要做一个引用(object.ReferenceEquals)测试,看看对象是不是null然后你打电话给Equals

          【讨论】:

            【解决方案9】:

            cdhowie 使用ReferenceEquals 赚钱,但值得注意的是,如果有人将null 直接传递给Equals,您仍然可以获得例外。此外,如果您要覆盖Equals,几乎总是值得实现IEquatable&lt;T&gt;,所以我会改为。

            public class Person : IEquatable<Person>
            {
              /* more stuff elided */
            
              public bool Equals(Person other)
              {
                return !ReferenceEquals(other, null) &&
                  SocialSecurityNumber == other.SocialSecurityNumber &&
                  FirstName == other.FirstName &&
                  LastName == other.LastName;
              }
              public override bool Equals(object obj)
              {
                return Equals(obj as Person);
              }
              public static bool operator !=(Person person1, Person person2)
              {
                return !(person1 == person2);
              }
              public static bool operator ==(Person person1, Person person2)
              {
                return ReferenceEquals(person1, person2)
                  || (!ReferenceEquals(person1, null) && person1.Equals(person2));
              }
            }
            

            当然,你永远不应该覆盖Equals,也不要覆盖GetHashCode()

            public override int GetHashCode()
            {
               //I'm going to assume that different
               //people with the same SocialSecurityNumber are extremely rare,
               //as optimise by hashing on that alone. If this isn't the case, change this
               return SocialSecurityNumber.GetHashCode();
            }
            

            同样值得注意的是,身份需要平等(也就是说,对于任何有效的“平等”概念,某物总是等于它自己)。由于相等性测试可能很昂贵并且发生在循环中,并且由于在实际代码中将某些内容与自身进行比较往往很常见(尤其是如果对象在多个地方传递),因此值得将其添加为快捷方式:

              public bool Equals(Person other)
              {
                return !ReferenceEquals(other, null) &&
                  ReferenceEquals(this, other) ||
                  (
                    SocialSecurityNumber == other.SocialSecurityNumber &&
                    FirstName == other.FirstName &&
                    LastName == other.LastName
                  );
              }
            

            ReferenceEquals(this, other) 上的捷径有多少好处可能会因课程的性质而有很大差异,但是否值得这样做是人们应该始终考虑的事情,所以我在这里包含了这项技术.

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2018-05-29
              • 2011-08-03
              • 1970-01-01
              • 1970-01-01
              • 2017-12-11
              • 2014-06-12
              • 1970-01-01
              • 2022-12-09
              相关资源
              最近更新 更多