Почему (object) 0 == (object) 0 отличается от ((object) 0) .Equals ((object) 0)?


117

Почему следующие выражения разные?

[1]  (object)0 == (object)0 //false
[2]  ((object)0).Equals((object)0) // true

На самом деле, я могу полностью понять [1], потому что, вероятно, среда выполнения .NET будет boxиспользовать целое число и вместо этого начнет сравнивать ссылки. Но почему [2] отличается?


36
Хорошо, теперь, когда вы понимаете ответ на этот вопрос, проверьте свое понимание, предсказав результат: short myShort = 0; int myInt = 0; Console.WriteLine("{0}{1}{2}", myShort.Equals(myInt), myInt.Equals(myShort), myInt == myShort); Теперь сравните его с реальностью. Ваш прогноз оказался верным? Если нет, то можете ли вы объяснить расхождение?
— Эрик Липперт

1
@Star для рекомендуемого чтения см msdn.microsoft.com/en-us/library/vstudio/... для доступных перегрузок на int16ака shortEquals метода, а затем посмотреть на msdn.microsoft.com/en-us/library/ms173105.aspx , Я не хочу портить головоломку Эрика Липперта, но ее будет довольно легко разгадать, когда вы прочтете эти страницы.
— Sam Skuce

2
Я думал, что это вопрос Java; по крайней мере, до того, как увидел букву «E» в Equals.
— seteropere

4
@seteropere Java на самом деле отличается: автобокс в Java кэширует объекты, поэтому ((Integer)0)==((Integer)0)оценивается как true.
— Жюль

1
Вы тоже можете попробовать IFormattable x = 0; bool test = (object)x == (object)x;. Никакая новая упаковка не выполняется, если структура уже находится в коробке.
— Йеппе Стиг Нильсен

Ответы:


151

Причина, по которой вызовы ведут себя по-разному, заключается в том, что они привязаны к очень разным методам.

==Дело будет связываться со статическим оператором ссылки равенства. Созданы 2 независимых intзначения в штучной упаковке, поэтому они не являются одной и той же ссылкой.

Во втором случае вы привязываетесь к методу экземпляра Object.Equals. Это виртуальный метод, который будет фильтровать Int32.Equalsи проверять на наличие упакованного целого числа. Оба целых значения равны 0, следовательно, они равны


==Случае не вызывается Object.ReferenceEquals. Он просто создает ceqинструкцию IL для выполнения сравнения ссылок.
— Сэм Харвелл

8
@ 280Z28 это не только потому, что компилятор встраивает это?
— markmnl

@ 280Z28 Итак? В аналогичном случае их метод Boolean.ToString, по-видимому, содержит жестко закодированные строки внутри своей функции, а не возвращает публично открытые Boolean.TrueString и Boolean.FalseString. Это не имеет значения; Дело в том, что ==делает то же самое, что и ReferenceEquals(по крайней мере, на Object). Это всего лишь внутренняя оптимизация на стороне MS, чтобы избежать ненужных внутренних вызовов функций для часто используемых функций.
— Nyerguds

6
В параграфе 7.10.6 Спецификации языка C # говорится: Предопределенные операторы равенства ссылочных типов: bool operator ==(object x, object y); bool operator !=(object x, object y);Операторы возвращают результат сравнения двух ссылок на равенство или неравенство. System.Object.ReferenceEqualsИспользование метода для определения результата не является обязательным . To @markmnl: Нет, компилятор C # не является встроенным, это то, что иногда вызывает дрожание (но не в этом случае). Так что 280Z28 прав, ReferenceEqualsметод фактически не используется.
— Йеппе Стиг Нильсен

@JaredPar: Интересно, что это сказано в спецификации, поскольку язык на самом деле ведет себя иначе. С учетом операторов, определенных, как указано выше, и переменных Cat Whiskers; Dog Fido; IDog Fred;(для несвязанных интерфейсов ICatи IDogнесвязанных классов Cat:ICatи Dog:IDog) сравнения Whiskers==Fidoи Whiskers==34будут законными (первое может быть истинным, только если Whiskers и Fido оба равны нулю; второе никогда не может быть истинным. ). Фактически, компилятор C # отклонит и то, и другое. Whiskers==Fred;будет запрещено, если Catзапечатано, но разрешено, если это не так.
— supercat

26

Когда вы приводите значение int 0(или любой другой тип значения) к object, значение помещается в рамку . Каждое приведение к objectсоздает другой блок (то есть другой экземпляр объекта). ==Оператор для objectпреформ типа ссылки сравнения, поэтому он возвращает ложь , так как левая сторона и правая не тот же экземпляр.

С другой стороны, когда вы используете Equals, который является виртуальным методом, он использует реализацию фактического упакованного типа, т. Int32.EqualsЕ. Который возвращает истину, поскольку оба объекта имеют одинаковое значение.


18

==Оператор, будучи статическим, не является виртуальным. Он будет запускать точный код, который objectопределяет класс (`object является типом времени компиляции операндов), который будет выполнять сравнение ссылок, независимо от типа среды выполнения любого объекта.

EqualsМетод представляет собой виртуальный метод экземпляра. Он будет запускать код, определенный в фактическом типе времени выполнения (первого) объекта, а не код в objectклассе. В этом случае объект является объектом int, поэтому он будет выполнять сравнение значений, поскольку это то, что intтип определяет для своего Equalsметода.


На ==самом деле токен представляет два оператора, один из которых является перегружаемым, а другой - нет. Поведение второго оператора сильно отличается от поведения перегрузки на (объект, объект).
— supercat

13

Equals()Метод является виртуальным.
Таким образом, он всегда вызывает конкретную реализацию, даже когда вызывается object. intпереопределяет Equals()сравнение по значению, поэтому вы получаете сравнение значений.


10

== Использование: Object.ReferenceEquals

Object.Equals сравнивает значение.

object.ReferenceEqualsМетод сравнивает ссылку. Когда вы выделяете объект, вы получаете ссылку, содержащую значение, указывающее его расположение в памяти, в дополнение к данным объекта в куче памяти.

object.EqualsМетод сравнивает содержимое объектов. Сначала он проверяет, равны ли ссылки, как и object.ReferenceEquals. Но затем он вызывает производные методы Equals для дальнейшей проверки равенства. Посмотри это:

   System.Object a = new System.Object();
System.Object b = a;
System.Object.ReferenceEquals(a, b);  //returns true

Несмотря на то, что Object.ReferenceEqualsведет себя как метод, использующий ==оператор C # в своих операндах, оператор оператора равенства ссылок C # (который представлен с использованием ==типов операндов, для которых не определена перегрузка) использует специальную инструкцию, а не вызывает ReferenceEquals. Кроме того, Object.ReferenceEqualsпримет операнды, которые могут совпадать, только если оба окажутся равными нулю, и примет операнды, которые должны быть приведены к типу Objectи, следовательно, не могут ничего сопоставить, в то время как версия равенства ссылок ==откажется компилировать такое использование ,
— supercat

9

Оператор C # использует токен ==для представления двух разных операторов: статически перегружаемого оператора сравнения и неперегружаемого оператора сравнения ссылок. Когда он встречает ==токен, он сначала проверяет, существует ли какая-либо перегрузка проверки равенства, которая применима к типам операндов. Если это так, он вызовет эту перегрузку. В противном случае он проверит, применимы ли типы к оператору сравнения ссылок. Если это так, он будет использовать этот оператор. Если ни один из операторов не применим к типам операндов, компиляция завершится ошибкой.

Код (Object)0не просто повышает Int32до Object:, Int32как и все типы значений, фактически представляет два типа, один из которых описывает значения и места хранения (например, буквальный ноль), но не является производным от чего-либо, а один из которых описывает кучи объекты и производные от Object; поскольку только последний тип может быть приведен к восходящему преобразованию Object, компилятор должен создать новый объект кучи этого последнего типа. Каждый вызов (Object)0создает новый объект кучи, поэтому два операнда to ==являются разными объектами, каждый из которых независимо инкапсулирует Int32значение 0.

Класс Objectне имеет каких-либо используемых перегрузок, определенных для оператора равенства. Следовательно, компилятор не сможет использовать перегруженный оператор проверки на равенство и вернется к использованию проверки на равенство ссылок. Поскольку два операнда ==относятся к различным объектам, он будет сообщать false. Второе сравнение завершается успешно, поскольку оно запрашивает у одного экземпляра объекта кучи Int32, равен ли он другому. Поскольку этот экземпляр знает, что значит быть равным другому отдельному экземпляру, он может ответить true.


Кроме того, каждый раз, когда вы пишете литерал 0в своем коде, я предполагаю, что для этого в куче создается объект типа int. Это не уникальная ссылка на одно глобальное статическое нулевое значение (например, как они сделали String.Empty, чтобы избежать создания новых пустых строковых объектов только для инициализации новых строк), поэтому я почти уверен, что даже выполнение a 0.ReferenceEquals(0)вернет false, поскольку оба 0 являются вновь созданные Int32объекты.
— Nyerguds

1
@Nyerguds, я почти уверен, что все, что вы сказали, неверно, о целых числах, куче, истории, глобальной статике и т. Д. Не 0.ReferenceEquals(0)удастся, потому что вы пытаетесь вызвать метод с постоянной времени компиляции. нет объекта, чтобы повесить его. Распакованный int - это структура, хранящаяся в стеке. Даже int i = 0; i.ReferenceEquals(...)работать не будет. Потому System.Int32что НЕ наследуется от Object.
— Эндрю Бэкер

@AndrewBacker System.Int32- это a struct, a structis System.ValueType, который сам наследует System.Object. Thatswhy есть ToString()способ и EqualsметодSystem.Int32
— Себастьяне

1
Тем не менее, Ньергудс ошибается, утверждая, что Int32 будет создан в куче, а это не так.
— Себастьян

@SebastianGodelet, я как бы игнорирую внутренности в этом. Сам System.Int32 реализует эти методы. GetType () - это extern Object, и здесь я давно перестал об этом беспокоиться. Дальше идти никогда не приходилось. AFAIK, CLR обрабатывает два типа по-разному и специально. Это не просто наследство. Однако это isодин из двух типов данных. Я просто не хотел, чтобы кто-нибудь прочитал этот комментарий и так сильно сбился с пути, включая эту странность с пустыми строками, которая игнорирует интернирование строк.
— Эндрю Бэкер

3

Обе проверки разные. Первый проверяет идентичность , второй - равенство . В общем, два термина идентичны, если они относятся к одному и тому же объекту. Это означает, что они равны. Два члена равны, если их значения совпадают.

С точки зрения программирования идентичность обычно искажается ссылочным равенством. Если указатели на оба термина равны (!), То объект, на который они указывают, точно такой же. Однако, если указатели разные, значение объектов, на которые они указывают, может быть одинаковым. В C # идентичность можно проверить с помощью статического Object.ReferenceEqualsчлена, в то время как равенство проверяется с помощью нестатического Object.Equalsчлена. Так как вы бросаете два целых числа к объектам (который называется «бокс», кстати), то operatior ==из objectпреформ первый чек, который по умолчанию отображается в Object.ReferenceEqualsи проверяет идентичность. Если вы явно вызываете нестатический Equalsчлен, динамическая отправка приводит к вызову Int32.Equals, который проверяет равенство.

Обе концепции похожи, но не одинаковы. Сначала они могут показаться запутанными, но небольшая разница очень важна! Представьте себе двух человек, а именно «Алису» и «Боба». Они оба живут в желтом доме. Исходя из предположения, что Алиса и Боб живут в районе, где дома различаются только цветом, они оба могут жить в разных желтых домах. Если вы сравните оба дома, вы поймете, что они абсолютно одинаковые, потому что оба желтые! Однако они не живут в одном доме, и поэтому их дома равны , но не идентичны . Идентичность подразумевает, что они живут в одном доме.

Примечание : некоторые языки определяют ===оператора для проверки идентичности.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.