Равно (элемент, ноль) или элемент == ноль - PullRequest
39 голосов
/ 18 августа 2010

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

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

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

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

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

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

Ответы [ 5 ]

58 голосов
/ 18 августа 2010

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

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

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

5 голосов
/ 18 августа 2010

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

2 голосов
/ 27 сентября 2013

Если вы хотите проверить 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 в этой ситуации, то вам нужен тест РАВЕНСТВО, а не тест IDENTITY. Смотрите следующую часть.


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

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

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

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

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

В. Могут ли они быть переопределены в каком-то классе с кодом, который неправильно обрабатывает нуль, что приводит к исключению?

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

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

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

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

2 голосов
/ 18 августа 2010

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 100 '* * == * * * * * * * * * * *1004*. неизменяемых объектов, для которых вы, вероятно, должны переопределить ==, чтобы быть равенством значений.

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

1 голос
/ 18 августа 2010

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

...