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