覆盖 == 运算符。如何与空值进行比较? [复制]

Posted

技术标签:

【中文标题】覆盖 == 运算符。如何与空值进行比较? [复制]【英文标题】: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 的目标是提供帮助,则应尽一切努力完全删除此页面,或使其尽可能好。 【参考方案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 无论如何都会内联这个调用;运行时性能将相同。见***.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) 上的捷径有多少好处可能会因课程的性质而有很大差异,但是否值得这样做是人们应该始终考虑的事情,所以我在这里包含了这项技术.

【讨论】:

以上是关于覆盖 == 运算符。如何与空值进行比较? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

那些年我们踩过的坑,SQL 中的空值陷阱!

那些年我们踩过的坑,SQL 中的空值陷阱!

JPA实体代理问题与空值比较

可选链与空值合并

pandas中表的合并与空值的使用

如何在 MySQL 5.7 中将 WHERE IN 与空值一起使用?