Переопределение == оператора. Как сравнить с нулем?
Возможный дубликат:
Как проверить наличие нулей в перегрузке оператора '==' без бесконечной рекурсии?
Вероятно, есть простой ответ на этот вопрос... но он, кажется, ускользает от меня. Вот упрощенный пример:
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
,