Equals (item, null) или item == null

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

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

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

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

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

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

Ответ 1

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

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

Короче говоря, вид поведения сравнения, который вы получаете, может значительно различаться в зависимости от того, какой метод вы выберете. Один комментарий здесь: Microsoft официально не документирует внутреннее поведение Object.Equals(a,b). Если вам нужна планка с железом, сравнивающая ссылку на null без какого-либо другого кода, вы хотите 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) ) { ... }

Поэтому в некоторых ограниченных случаях использование == является безопасным и соответствующим.

Ответ 2

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

Ответ 3

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

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

Ответ 4

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

Ответ 5

Если вы хотите протестировать 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. См. Следующую часть.


Если вы хотите протестировать EQUALITY (то же содержимое):

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

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

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

СЕЙЧАС, чтобы задать исходный вопрос:

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

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

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

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

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