2010-08-17 1 views
32

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

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

более
if (Equals(item, null)) { /* Do Something */ } 

надежными, чем это:

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

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

ответ

43

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

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

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

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

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

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

Обратите внимание, что производный тип может переопределить метод 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() здесь не лучше. В то время как реализация по умолчанию выполняет проверку ссылок/побитовое равенство, тип может переопределить метод члена , и в этом случае эта реализация будет вызвана. Реализация, предоставленная пользователем, может вернуть все, что захочет, даже при сравнении с нулевым.

Но как насчет статической версии 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 сравнения ссылку на нуль без какой-либо другой код работает, вы хотите Object.ReferenceEquals():

Object.ReferenceEquals(item, null); 

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

«Эй, это неудобно, давайте заменим его: a.Equals(null) или a == null

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

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

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

Это ненужно (и некрасиво) написать:

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

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

+0

Я думал, что мой ответ (ответ Microsoft по доверенности) - довольно простой ответ. – mquander

+4

Вопросы типа: * «Должен ли я всегда/никогда не делать X» * подразумевать разрыв в знаниях о нюансах рассматриваемого предмета. Я чувствовал, что немного более подробно было бы полезно здесь, чтобы прояснить, почему я не думаю, что простой ответ имеет смысл. – LBushkin

+0

Статический метод 'object.Equals (a, b)' может фактически вызвать перегруженный/виртуальный метод «Equals» на объекте 'a'. Если я правильно помню, он делает что-то вроде '((object) a == (объект) b) || (a! = null && b! = null && a.Equals (b)) '. (Это не имеет значения, если сравнивать с «null», о чем спрашивает OP, но имеет отношение к общему случаю.) – LukeH

4

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

+0

Возможно, кто-то перегрузит оператор '==' без переопределения 'Equals'. Это было бы плохой дизайн, но вполне возможно, что семантика сравнения равенства будет отличаться между ними.Кроме того, если оператор '==' перегружен, он может сделать что-то совершенно отличное от встроенного метода Objects.Equals() ', который, я считаю, просто проверяет ссылочное равенство. – LBushkin

2

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

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

+2

В руководствах Microsoft не рассматриваются проблемы с нулями, о чем и идет речь. Самое главное, в то время как «myString == null» является безопасным тестом, «myString.Equals (null)» приведет к исключению, когда myString имеет значение null. Кроме того, в директивах Microsoft не упоминается также различие между Object.Equals (myObject, b) и myObject.Equals (b). Первый - надежный; последнее дает исключение, если myObject имеет значение null. Таким образом, хотя ссылка, которую вы предоставляете, полезна, она НЕ является ответом на вопрос плаката. – ToolmakerSteve

1

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

+0

** Это утверждение не совсем корректно. ** Для реализации оператора '==' для типа можно вызвать виртуальный метод в одном из задействованных экземпляров. Это означает, что это действительно возможно для событий, которые не основаны на типах выражения, а скорее на задействованных типах исполнения. – LBushkin

+0

То же самое относится и к статическому методу 'object.Equals (a, b)', который делает что-то вроде '((object) a == (object) b) || (a! = null && b! = null && a.Equals (b)), то есть он может вызвать виртуальный метод «Equals» на объекте 'a'. – LukeH

+0

@LBushkin Ваша точка действительна, однако это не равно (простить каламбур), мое утверждение «не совсем правильно». –

1

Если вы хотите проверить 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.Equals(b)». Например, «this.Equals (b)» является безопасным. Или, если «a» - это поле, которое инициализируется во время построения, и конструктор генерирует исключение, если значение null передано как значение, которое будет использоваться в этом поле.

сейчас, чтобы решить оригинальный вопрос:

Вопрос: Являются ли эти восприимчивы к переопределяются в некотором классе, с кодом, который не обрабатывает нуль правильно, в результате исключения?

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

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

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

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