Чому (об’єкт) 0 == (об’єкт) 0 відрізняється від ((об’єкт) 0). Рівності ((об’єкт) 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/… про доступні перевантаження за методом int16aka Equalsshort , а потім подивіться на msdn.microsoft.com/en-us/library/ms173105.aspx . Я не хочу псувати головоломку Еріка Ліпперта, але це буде досить легко зрозуміти, коли ти прочитаєш ці сторінки.
Сем Скуче

2
Я подумав, що це питання java; принаймні перед тим, як побачити "Е" в рівних.
seteropere

4
@seteropere Java насправді інша: автобокс в Java кешує об’єкти, тому ((Integer)0)==((Integer)0)оцінюється як істинний.
Жуль

1
Ви також можете спробувати IFormattable x = 0; bool test = (object)x == (object)x;. Новий бокс не виконується, коли структура вже в коробці.
Джеппе Стіг Нільсен

Відповіді:


151

Причина, по якій дзвінки поводяться по-різному, полягає в тому, що вони пов'язуються з дуже різними методами.

==Справа буде зв'язуватися зі статичним оператором посилання рівності. intСтворено 2 незалежних коробкових значення, отже вони не є однаковими посиланнями.

У другому випадку ви прив'язуєтесь до методу екземпляра Object.Equals. Це віртуальний метод, який буде відфільтровувати до Int32.Equalsцього, і це перевіряє наявність у коробці цілого числа. Обидва цілих значення 0, отже, вони рівні


Справа ==не дзвонить Object.ReferenceEquals. Він просто виробляє ceqінструкцію IL для виконання порівняльного посилання.
Сем Харвелл

8
@ 280Z28 це не лише тому, що компілятор накреслює це?
markmnl

@ 280Z28 Отже? Аналогічний випадок, що їх метод Boolean.ToString, очевидно, містить твердо кодовані рядки всередині своєї функції, а не повертає загальнодоступні Boolean.TrueString та Boolean.FalseString. Це не має значення; Справа в тому, ==чи робить те саме, що і ReferenceEquals(на Об'єкті все одно). Це все лише внутрішня оптимізація на стороні MS, щоб уникнути непотрібних внутрішніх викликів функцій часто використовуваних функцій.
Nyerguds

6
У параграфі 7.10.6 специфікації мови C # зазначено: Заздалегідь заданими операторами рівності опорного типу є: bool operator ==(object x, object y); bool operator !=(object x, object y);Оператори повертають результат порівняння двох посилань на рівність або нерівність. Це не вимога, що метод System.Object.ReferenceEqualsвикористовується для визначення результату. До @markmnl: Ні, компілятор C # не вбудований, це те, що іноді робить тремтіння (але не в цьому випадку). Тож 280Z28 правильно, ReferenceEqualsметод насправді не використовується.
Jeppe Stig Nielsen

@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визначає клас (`об'єкт є типом часу компіляції операндів), який буде робити порівняльне порівняння, незалежно від типу часу виконання будь-якого об'єкта.

EqualsМетод являє собою віртуальний метод примірника. Він буде виконувати код, визначений у фактичному типі виконання (першого) об'єкта, а не код у objectкласі. У цьому випадку об'єкт є an int, тому він буде виконувати порівняння значень, оскільки саме це intвизначає тип для його Equalsметоду.


==Маркер на насправді являє собою два оператора, один з яких є Перенавантажувані і один з яких не є. Поведінка другого оператора сильно відрізняється від поведінки перевантаження на (об'єкт, об'єкт).
supercat

13

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


10

== Використання: Object.ReferenceEquals

Object.Equals порівнює значення.

object.ReferenceEqualsМетод порівнює посилання. Виділяючи об'єкт, ви отримуєте посилання, що містить значення, що вказує його місце в пам'яті, крім даних об’єкта в купі пам'яті.

object.EqualsМетод порівнює вміст об'єктів. Він спочатку перевіряє, чи однакові посилання, як і об'єкт.ReferenceEquals. Але тоді він запускає похідні методи рівного рівня для перевірки рівності. Дивіться це:

   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створює новий об'єкт купи, тому два операнди - ==це різні об'єкти, кожен з яких незалежно інкапсулює Int32значення 0.

Клас Objectне має корисних перевантажень, визначених для оператора рівних. Отже, компілятор не зможе використати перевантажений оператор тесту на рівність і перейде до використання тесту рівності еталонної рівності. Оскільки два операнди ==посилаються на окремі об'єкти, вони звітуватимуть false. Друге порівняння є успішним, оскільки воно запитує один екземпляр купи об'єкта, Int32чи він рівний іншому. Оскільки цей екземпляр знає, що означає бути рівним іншому окремому екземпляру, він може відповісти true.


Крім того, кожного разу, коли ви пишете буквал 0у своєму коді, я припускаю, що він робить це int об'єктом у купі для цього. Це не унікальна посилання на одне глобальне статичне нульове значення (наприклад, як вони зробили String.Empty, щоб уникнути створення нових порожніх рядкових об’єктів лише для ініціалізації нових рядків). Тому я досить впевнений, що навіть при виконанні "a 0.ReferenceEquals(0)" повернеться false, оскільки обидва 0 новостворені Int32об’єкти.
Nyerguds

1
@Nyerguds, я впевнений, що все, що ви сказали, є невірним, про ints, купу, історію, глобальну статику і т. Д. Не 0.ReferenceEquals(0)вдасться, оскільки ви намагаєтеся викликати метод на константі часу компіляції. немає жодного об’єкта, щоб повісити його. Unboxed int - це структура, що зберігається в стеці. Навіть int i = 0; i.ReferenceEquals(...)не вийде. Тому System.Int32що НЕ успадковує від Object.
Ендрю Бекер

@AndrewBacker, System.Int32це а struct, structє System.ValueType, що сам успадковує System.Object. Ось чому є ToString()метод і Equalsметод дляSystem.Int32
Себастьян

1
Тим не менш, Nyerguds неправильно стверджує, що Int32 буде створений на купі, що не так.
Себастьян

@SebastianGodelet, я якось ігнорую внутрішніх справ у цьому. System.Int32 реалізує ці методи. GetType () - це зовнішній вигляд 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.