148

可能重复:
如何在没有无限递归的情况下检查“==”运算符重载中的空值?

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

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

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

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

4

9 回答 9

268

使用object.ReferenceEquals(person1, null)or new is 运算符代替==运算符:

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

    return person1.Equals(person2);
}
于 2010-11-18T20:31:38.540 回答
21

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

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

编辑
我修改了==操作符函数以匹配微软在此处建议的实现。

于 2010-11-18T20:38:38.577 回答
12

你总是可以覆盖并放

(Object)(person1)==null

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

于 2010-11-18T20:32:26.173 回答
5

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

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

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

于 2010-11-19T02:17:23.857 回答
3

最后的(假设的)例程如下。这与@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 执行 null 检查

于 2010-11-18T21:26:45.073 回答
2

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);
}
于 2010-11-18T20:33:47.517 回答
2

将 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.
于 2010-11-18T20:36:00.870 回答
1

始终如一地重载这些运算符非常困难。我对相关问题的回答可以作为模板。

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

于 2010-11-18T20:33:24.397 回答
1

cdhowie 使用 是赚钱的ReferenceEquals,但值得注意的是,如果有人null直接传递给Equals. 此外,如果您要覆盖Equals它几乎总是值得实施IEquatable<T>,所以我会改为。

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)可能会因课程的性质而有很大差异,但是是否值得做是一个应该始终考虑的事情,所以我在这里包括了这项技术。

于 2010-11-19T01:25:48.297 回答