Оператор переопределения ==. Как сравнить с null?

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

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

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

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

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

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

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

Ответ 1

Используйте 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);
}

Ответ 2

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

(Object)(person1)==null

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

Ответ 3

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

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 версией здесь.

Ответ 4

Вставьте экземпляр 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);
}

Ответ 5

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

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

Ответ 6

Перенесите Person в объект и затем выполните сравнение:

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.

Ответ 7

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

Ответ 8

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

Ответ 9

Легче любого из этих подходов было бы просто использовать

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

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