Переопределение == оператора. Как сравнить с нулем?

Возможный дубликат:
Как проверить наличие нулей в перегрузке оператора '==' без бесконечной рекурсии?

Вероятно, есть простой ответ на этот вопрос... но он, кажется, ускользает от меня. Вот упрощенный пример:

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()

вызов метода, поскольку вы не можете вызвать метод для нулевого экземпляра.

С другой стороны, вы не можете явно проверить это условие внутри переопределения ==, так как это приведет к бесконечной рекурсии (и переполнению стека [точка 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);
}

Итак, как вы переопределяете операторы == и!= Для равенства значений и по-прежнему учитываете нулевые объекты?

Я надеюсь, что ответ не до боли прост.:-)

9 ответов

Решение

Использование object.ReferenceEquals(person1, null) вместо == оператор:

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

    return person1.Equals(person2);
}

Я всегда делал это таким образом (для операторов == и!=) И повторяю этот код для каждого создаваемого мной объекта:

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

редактировать
Я модифицировал == Функция оператора, чтобы соответствовать предложенной Microsoft реализации здесь.

Вы могли бы всегда переопределить и положить

(Object)(person1)==null

Я предположил бы, что это будет работать, хотя не уверен.

Проще, чем любой из этих подходов, было бы просто использовать

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

Это имеет ту же семантику нулевого равенства, что и подходы, которые предлагают все остальные, но проблема в фреймворке - выяснить детали:)

Финальная (гипотетическая) рутина приведена ниже. Это очень похоже на первый принятый ответ @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

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) Это может значительно варьироваться в зависимости от природы класса, но стоит ли это делать или нет, это то, что всегда нужно учитывать, поэтому я включу технику здесь.

Приведите человека к объекту, а затем выполните сравнение:

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.

Брось 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);
}

Постоянно перегружать эти операторы довольно сложно. Мой ответ на связанный вопрос может служить шаблоном.

В принципе, сначала нужно сделать ссылку (object.ReferenceEquals) проверить, является ли объект null, Тогда вы звоните Equals,

Другие вопросы по тегам