Равно (item, null) или item == null

- это код, который использует статический объект.Равно чтобы проверить на null более надежным, чем код, который использует оператор == или обычный объект.Равно? Не являются ли последние два уязвимыми для переопределения таким образом, что проверка на null не работает должным образом (например, возврат false, когда сравниваемое значение is null)?

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

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

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

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

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

5 ответов


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

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

но как насчет статической версии 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). Если вам нужен железный gaurantee сравнения ссылки на 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) ) { ... }

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


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


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

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


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


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

ReferenceEquals(a, b)

обрабатывает значения null. И не является переопределяемым. 100% безопасный.

но убедитесь, что вы действительно хотите проверить личность. Рассмотрим следующее:

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

возвращает false. В отличие от этого:

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

и

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

как вернуть true.

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


когда вы хотите проверить равенство (то же содержимое):

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

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

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

теперь, чтобы обратиться к оригиналу вопрос:

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

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

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

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

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