it-roy-ru.com

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

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

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

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

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

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

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

37
Joseph Sturtevant

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

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

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

54
LBushkin

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

4
Michael Burr

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

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

2
mquander

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

1
Daniel Pratt

Когда вы хотите проверить 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 - это предварительно протестировать нули самостоятельно.

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

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

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

1
ToolmakerSteve