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

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

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

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

более устойчиво, чем это:

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

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

39
задан Joseph Sturtevant 17 August 2010 в 22:11
поделиться

4 ответа

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

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

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

Нет необходимости (и некрасиво) писать:

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

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

63
ответ дан 27 November 2019 в 02:32
поделиться

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

5
ответ дан 27 November 2019 в 02:32
поделиться

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

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

2
ответ дан 27 November 2019 в 02:32
поделиться

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

1
ответ дан 27 November 2019 в 02:32
поделиться
Другие вопросы по тегам:

Похожие вопросы: