Равно (элемент, ноль) или элемент == ноль

Является ли код, который использует статические Object.Equals для проверки на null, более надежным, чем код, который использует оператор == или обычные Object.Equals? Разве последние два не подвержены переопределению таким образом, что проверка на нулевое значение не работает должным образом (например, возвращает false, если сравниваемое значение равно нулю)?

Другими словами, это:

if (Equals(item, null)) { /* Do Something */ }

более надежный, чем этот:

if (item == null) { /* Do Something */ }

Я лично считаю, что последний синтаксис легче читать. Следует ли этого избегать при написании кода, который будет обрабатывать объекты вне контроля автора (например, библиотеки)? Следует ли этого всегда избегать (при проверке на ноль)? Это просто раскалывание волос?

7 ответов

Решение

Там нет простого ответа на этот вопрос. Любой, кто говорит, что всегда используйте один или другой, дает вам плохой совет, по моему мнению.

На самом деле есть несколько различных методов, которые вы можете вызвать для сравнения экземпляров объекта. Даны два экземпляра объекта a а также b Вы могли бы написать:

  • Object.Equals(a,b)
  • Object.ReferenceEquals(a,b)
  • a.Equals(b)
  • a == b

Все они могут делать разные вещи!

Object.Equals(a,b) будет (по умолчанию) выполнять сравнение равенства ссылок на ссылочных типах и побитовое сравнение на типах значений. Из документации MSDN:

Реализация по умолчанию Equals поддерживает равенство ссылок для ссылочных типов и побитовое равенство для типов значений. Равенство ссылок означает, что сравниваемые ссылки на объекты ссылаются на один и тот же объект. Побитовое равенство означает, что сравниваемые объекты имеют одинаковое двоичное представление.

Обратите внимание, что производный тип может переопределять метод Equals для реализации равенства значений. Равенство значений означает, что сравниваемые объекты имеют одинаковое значение, но разные двоичные представления.

Обратите внимание на последний абзац выше... мы обсудим это чуть позже.

Object.ReferenceEquals(a,b) выполняет только сравнение на равенство. Если переданные типы являются типами в штучной упаковке, результат всегда false,

a.Equals(b) вызывает метод виртуального экземпляра Object какой тип a может переопределить, чтобы сделать все, что он хочет. Вызов выполняется с использованием виртуальной диспетчеризации, поэтому код, который выполняется, зависит от типа среды выполнения. a,

a == bвызывает статический перегруженный оператор типа ** времени компиляции * a, Если реализация этого оператора вызывает методы экземпляра на любом a или же b, это также может зависеть от типов времени выполнения параметров. Поскольку отправка основана на типах в выражении, следующее может дать разные результаты:

Frog aFrog = new Frog();
Frog bFrog = new Frog();
Animal aAnimal = aFrog;
Animal bAnimal = bFrog;
// not necessarily equal...
bool areEqualFrogs = aFrog == bFrog;
bool areEqualAnimals = aAnimal = bAnimal;

Так что, да, существует уязвимость для проверки нулей с помощью operator == , На практике большинство типов не перегружают == - но нет никакой гарантии.

Метод экземпляра Equals() здесь не лучше В то время как реализация по умолчанию выполняет проверку на равенство / битовую проверку, для типа возможно переопределить Equals() метод-член, в этом случае эта реализация будет вызвана. Предоставленная пользователем реализация может вернуть все, что захочет, даже при сравнении с нулем.

Но как насчет статической версии Object.Equals() ты спрашиваешь? Может ли это в конечном итоге запустить пользовательский код? Что ж, получается, что ответ - ДА. Реализация Object.Equals(a,b) расширяется во что-то вроде:

((object)a == (object)b) || (a != null && b != null && a.Equals(b))

Вы можете попробовать это для себя:

class Foo {
    public override bool Equals(object obj) { return true; }  }

var a = new Foo();
var b = new Foo();
Console.WriteLine( Object.Equals(a,b) );  // outputs "True!"

Как следствие, возможно утверждение: Object.Equals(a,b) запускать код пользователя, когда ни один из типов в вызове null, Обратите внимание, что Object.Equals(a,b) не вызывает версию экземпляра Equals() когда любой из аргументов равен нулю.

Короче говоря, тип поведения сравнения может значительно различаться в зависимости от того, какой метод вы выбрали для вызова. Однако один комментарий здесь: Microsoft официально не документирует внутреннее поведение Object.Equals(a,b), Если вам нужна железная гарантия сравнения ссылки на ноль без запуска какого-либо другого кода, вы хотите Object.ReferenceEquals() :

Object.ReferenceEquals(item, null);

Этот метод делает цель совершенно ясной - вы ожидаете, что результатом будет сравнение двух ссылок на равенство ссылок. Выгода здесь по сравнению с использованием чего-то вроде Object.Equals(a,null), что менее вероятно, что кто-то придет позже и скажет:

"Эй, это неловко, давайте заменим это на: a.Equals(null) или же a == null

которые потенциально могут быть разными.

Впрочем, давайте привнесем здесь некоторый прагматизм. До сих пор мы говорили о возможности различных способов сравнения для получения разных результатов. Хотя это, безусловно, имеет место, есть определенные типы, где безопасно писать a == null, Встроенные классы.NET, такие как String а также Nullable<T> иметь четкую семантику для сравнения. Кроме того, они sealed - предотвращение любых изменений их поведения посредством наследования. Следующее довольно распространено (и правильно):

string s = ...
if( s == null ) { ... }

Нет необходимости (и некрасиво) писать:

if( ReferenceEquals(s,null) ) { ... }

Так что в определенных ограниченных случаях, используя == безопасно и уместно.

if (Equals(item, null)) не более надежный, чем if (item == null)и я нахожу это более запутанным для загрузки.

Когда вы хотите проверить IDENTITY (то же место в памяти):

ReferenceEquals(a, b)

Обрабатывает нули. И не подлежит замене. 100% безопасно.

Но убедитесь, что вы действительно хотите тест IDENTITY. Учтите следующее:

ReferenceEquals(new String("abc"), new String("abc"))

который возвращается false, По сравнению:

Object.Equals(new String("abc"), new String("abc"))

а также

(new String("abc")) == (new String("abc"))

оба возвращаются true,

Если вы ожидаете ответа true в этой ситуации вам нужен тест EQUALITY, а не IDENTITY. Смотрите следующую часть.


Когда вы хотите проверить РАВЕНСТВО (то же содержание):

  • Используйте "a == b"если компилятор не жалуется.

  • Если это отклонено (если тип переменной a не определяет оператор "=="), тогда используйте "Object.Equals(a, b)".

  • Если вы находитесь внутри логики, где известно, что a не равно нулю, ТОГДА вы можете использовать более читабельное "a.Equals(b)". Например," this.Equals (b) "является безопасным. Или, если" a "является полем, которое инициализируется во время построения, и конструктор выдает исключение, если в качестве значения, которое будет использоваться в этом поле, передается значение null.,

СЕЙЧАС, чтобы ответить на оригинальный вопрос:

Q: Они могут быть переопределены в некотором классе, с кодом, который не обрабатывает нуль правильно, приводя к исключению?

A: Да. Единственный способ получить 100% -й безопасный тест EQUALITY - это предварительно протестировать нули самостоятельно.

А вы должны? Ошибка в этом (гипотетический будущий плохой класс), и это будет простой тип отказа. Легко отлаживать и исправлять (кто бы ни поставлял класс). Я сомневаюсь, что это проблема, которая часто случается или сохраняется долго, когда это происходит.

Более подробно А: Object.Equals(a, b) скорее всего, будет работать перед лицом плохо написанного класса. Если "а" равно нулю, класс Object будет обрабатывать его сам, поэтому никакого риска нет. Если "b" равно нулю, то тип "a" DYNAMIC (во время выполнения, а не во время компиляции) определяет, какой метод "Equals" вызывается. Вызванный метод просто должен работать правильно, когда "b" равно нулю. Если вызываемый метод не написан крайне плохо, первый шаг, который он делает, это определяет, является ли "b" типом, который он понимает.

Так Object.Equals(a, b) разумный компромисс между читабельностью /coding_effort и безопасностью.

Основные принципы предполагают, что вы относитесь к Equals как равенство значений (проверка, чтобы увидеть, представляют ли два объекта одну и ту же информацию, т.е. сравнение свойств), и == в качестве эталонного равенства, за исключением неизменяемых объектов, для которых вы, вероятно, должны переопределить == быть ценностным равенством.

Таким образом, предполагая, что руководящие принципы применимы здесь, выберите то, что семантически целесообразно. Если вы имеете дело с неизменяемыми объектами и ожидаете, что оба метода приведут к одинаковым результатам, я бы использовал == для ясности.

В отношении "... написания кода, который будет обрабатывать объекты вне контроля автора...", я бы отметил, что оба статических Object.Equals и == Операторы являются статическими методами и поэтому не могут быть виртуальными / переопределенными. Какая реализация вызывается, определяется во время компиляции на основе статического типа (типов). Другими словами, внешняя библиотека не может предоставить другую версию подпрограммы для вашего скомпилированного кода.

Сделайте это, поскольку два нуля нельзя сравнивать.

      (a == null ? "NA" : a).equals(b == null ? "NA" : b)

Я оказался здесь, когда пытался сравнить уникальный Id объектов, которые сами могли быть нулевыми. Было обнаружено, что проще сначала просто приписать недостающие данные, а затем провести сравнение.

Guid currentId = (Object1 == null) ? Guid.Empty : Object1.Id;
Guid newId = (Object2 == null) ? Guid.Empty : Object2.Id;
If (currentId == newId)
{
    //do happyface
}
else
{
   //do sadface
}
Другие вопросы по тегам