Різниця C # == і дорівнює ()


548

У мене є умова програми Silverlight, яка порівнює 2 рядки, чомусь коли я використовую, ==вона повертає помилку, а .Equals()повертає істину .

Ось код:

if (((ListBoxItem)lstBaseMenu.SelectedItem).Content.Equals("Energy Attack"))
{
    // Execute code
}

if (((ListBoxItem)lstBaseMenu.SelectedItem).Content == "Energy Attack")
{
    // Execute code
}

Будь-яка причина, чому це відбувається?



8
Рядок переосмислює ==, але оператори не є поліморфними. У цьому коді ==оператор викликається на тип object, який робить порівняння ідентичності замість значення.
Дрю Ноакс

12
Щоб розширити коментар @DrewNoakes: Компілятор вибирає ==перевантаження на основі типу часу компіляції операндів. ContentВластивість object. Оператори не є віртуальними, тому реалізація за замовчуванням ==викликається, даючи порівняльне порівняння порівняння. При рівності виклик переходить до віртуального методу object.Equals(object); stringпереосмислює цей метод і виконує порядкове порівняння за вмістом рядка. Див. Msdn.microsoft.com/en-us/library/fkfd9eh8(v=vs.110).aspx та referenceources.microsoft.com/#mscorlib/system/string.cs,507 .
фог

6
@ foog пояснення точне. Слід зазначити, що коли лівий бік ==має тип часу компіляції, objectа правий має тип часу компіляції string, тоді компілятор C # повинен вибрати (в даному випадку проблематичне) перевантаження operator ==(object, object); але він буде видавати попередження у час компіляції , що це може бути непередбачені. Тож читайте попередження про час збирання! Щоб виправити проблему та використовувати її ==, відведіть ліву частину до string. Якщо я пам'ятаю правильно, текст попередження говорить саме про це.
Jeppe Stig Nielsen

1
@JeppeStigNielsen +1 за порадою прочитати попередження компілятора. Ще краще: увімкніть опцію попереджень як помилок, щоб змусити всіх звертати на них увагу.
фог

Відповіді:


429

Якщо ==використовується для виразу типу object, воно вирішиться для System.Object.ReferenceEquals.

Equalsє лише virtualметодом і поводиться як такий, тому буде використана переосмислена версія (яка для stringтипу порівнює вміст).


56
Якщо оператор спеціально не реалізований у класі
Домінік Кронін

23
@DominicCronin Це неправда. Навіть якщо == реалізований у класі, він буде ігноруватися, оскільки тип зліва від порівняння є об’єктом. Схоже, перевантаження оператора визначаються під час компіляції, а під час компіляції все, що він знає, - це те, що ліва сторона є об’єктом.
MikeKulls

4
@DominicCronin Я вважаю, що ваше перше твердження правильне в тому, що == вирішить заперечити, але ваше друге твердження про те, що перевантаження оператора вирішується аналогічно. Вони сильно відрізняються, і тому .Equals буде вирішувати рядок, тоді як == буде вирішувати заперечення.
MikeKulls

8
Щоб було зрозуміло, objectтип (помічайте шрифт моноспростору) технічно означає "вираження типу System.Object". Це не має нічого спільного з типом виконання, який посилається на вираз. Я думаю, що твердження "визначені користувачем оператори трактуються як virtualметоди" є вкрай оманливим. Вони трактуються як перевантажені методи і залежать лише від типу компіляції часу операндів. Насправді, після того, як буде обчислено набір визначених користувачем операторів, решта процедури прив’язки буде саме алгоритмом вирішення перевантаження методу
Мехрдад Афшарі

4
@DominicCronin Оманлива частина полягає в тому, що virtualроздільна здатність методу залежить від фактичного типу виконання екземпляра, тоді як це повністю ігнорується в роздільній здатності перевантаження оператора, і це справді вся суть моєї відповіді.
Мехрдад Афшарі

314

При порівнянні посилання на об'єкт на рядок (навіть якщо посилання на об'єкт посилається на рядок), особлива поведінка ==оператора, специфічного для класу рядків, ігнорується.

Зазвичай (якщо справа не стосується рядків, тобто) Equalsпорівнює значення , а ==порівнює об'єктні посилання . Якщо два об'єкти, які ви порівнюєте, посилаються на один і той самий точний екземпляр об'єкта, то обидва повернуть істину, але якщо один має однаковий вміст і прийшов з іншого джерела (це окремий екземпляр з тими ж даними), тільки рівний повернути правду. Однак, як зазначається в коментарях, рядок є особливим випадком, оскільки він переосмислює ==оператора, так що при чистому посиланні на рядки (а не посилання на об'єкти), порівнюються лише значення, навіть якщо це окремі екземпляри. Наступний код ілюструє тонкі відмінності в поведінці:

string s1 = "test";
string s2 = "test";
string s3 = "test1".Substring(0, 4);
object s4 = s3;
Console.WriteLine("{0} {1} {2}", object.ReferenceEquals(s1, s2), s1 == s2, s1.Equals(s2));
Console.WriteLine("{0} {1} {2}", object.ReferenceEquals(s1, s3), s1 == s3, s1.Equals(s3));
Console.WriteLine("{0} {1} {2}", object.ReferenceEquals(s1, s4), s1 == s4, s1.Equals(s4));

Вихід:

True True True
False True True
False False True

8
Пляма на. Оператор '==' порівнює посилання на об'єкти (дрібне порівняння), тоді як .Equals () порівнює об'єктний вміст (глибоке порівняння). Як сказав @mehrdad, .Equals () буде відмінено для забезпечення глибокого порівняння вмісту.
Андрій

1
Я залишу цю посаду тут, бо вважаю, що цілком важливо підкреслити те, що не відбувається, оскільки вам потрібно приділяти пильну увагу, щоб це усвідомити. (І я думаю, що код для демонстрації правильних і неправильних розумінь теж вартий.) Я сподіваюся, що рейтинг не
знизиться

5
Напевно, String реалізує спеціальний оператор ==. Якби не тоді, використовуючи ==, не порівнював би вміст. Тож String - це поганий приклад для використання тут, оскільки він не допомагає нам зрозуміти загальний випадок, коли не визначений спеціальний оператор.
Домінік Кронін

6
+1 для прикладу епічного коду, який дав мені сенс цьому. Показує загальний випадок статичного типу (тип лівої сторони), що є об'єктом, і конкретний випадок статичного типу (/ тип RHS), що є рядком. І добре стосується строкового інтернування.
барлоп

2
@badsamaritan Через струнне інтернування
Олександр Дерк

46

==і .Equalsзалежать від поведінки, визначеної у фактичному типі та фактичному типі на сайті виклику. Обидва - це лише методи / оператори, які можуть бути переохолоджені на будь-який тип та мати будь-яку поведінку, якої бажає автор. З мого досвіду, я вважаю, що люди звичайно реалізують .Equalsна об'єкті, але нехтують оператором реалізації ==. Це означає, що .Equalsнасправді буде вимірювати рівність значень, тоді як ==буде вимірювати, чи є вони однаковою базою.

Коли я працюю з новим типом, визначення якого знаходиться в потоці або пише загальні алгоритми, я вважаю, що найкраща практика полягає в наступному

  • Якщо я хочу порівняти посилання на C #, я використовую Object.ReferenceEqualsбезпосередньо (не потрібен у загальному випадку)
  • Якщо я хочу порівняти використовувані значення EqualityComparer<T>.Default

У деяких випадках, коли я вважаю, що використання цього ==є неоднозначним, я буду явно використовуватиObject.Reference в коді рівності, щоб усунути неоднозначність.

Нещодавно Ерік Ліпперт зробив публікацію в блозі на тему, чому в CLR є два способи рівності. Варто прочитати


Ну, Джареде, ти прямо порушуєш знаменитий Джефф "Найкращий код тут взагалі немає коду". Це справді виправдано? З іншого боку, я бачу, звідки це походить, і чому, можливо, хочеться зробити явну семантику. У цьому випадку я дуже віддаю перевагу способу VB поводження з об'єктною рівністю. Це коротко і однозначно.
Конрад Рудольф

@Konrad, я дійсно повинен був сказати, "коли я незнайомий з типом, я вважаю, що найкраща практика полягає в наступному". Так, VB має тут набагато кращу семантику, оскільки вона справді розділяє значення та референтну рівність. C # змішує два разом, і це час від часу викликає помилки неоднозначності.
JaredPar

10
Це не зовсім вірно. == неможливо змінити, це статичний метод. Її можна лише перевантажувати, що є важливою відмінністю. Таким чином, код, який виконується для оператора ==, пов'язаний під час компіляції, тоді як рівний - віртуальний і знаходиться під час виконання.
Стефан Штайнгер

20

== Оператор

  1. Якщо операнди - це типи значень та їх значення рівні, вони повертають істинне інше хибне.
  2. Якщо операнди є типовими типами, за винятком рядка, і обидва посилаються на один і той же екземпляр (той самий об'єкт), він повертає істинне інше хибне.
  3. Якщо операнди мають рядковий тип і їх значення рівні, він повертає істинне інше хибне.

.Equals

  1. Якщо операнди є еталонними типами , він виконує еталонне рівність , тобто якщо обидва посилаються на один і той же екземпляр (один і той же об'єкт), він повертає істинне інше хибне.
  2. Якщо операнди - це типи значень, то на відміну від оператора ==, він спочатку перевіряє їх тип, а якщо їх типи однакові, він виконує == оператор інше, він повертає помилкове значення.

2
Це неправильно. ==Оператор може бути перевантажений для будь-якого типу, а не тільки рядки. Опис винятку в окремому випадку лише для рядкових помилок представляє семантику оператора. Було б більш точно, хоч, можливо, і не дуже корисно, сказати: "якщо операнди є типовими типами, він повертається істинним, якщо операнди посилаються на один і той же об'єкт, якщо не існує застосовного перевантаження, і в цьому випадку реалізація цього перевантаження визначає результат ". Те саме стосується і Equalsдоданого ускладнення, що це віртуальний метод, тому його поведінку можна перекрити, а також перевантажити.
фог

19

По-перше, є різниця. Для чисел

> 2 == 2.0
True

> 2.Equals(2.0)
False

А для струн

> string x = null;
> x == null
True

> x.Equals(null)
NullReferenceException

В обох випадках ==веде себе більш корисно, ніж.Equals


2
Я не впевнений, що вважав би примус цілісних типів до типів з плаваючою комою ==оператором доброю справою. Наприклад, чи повинен 16777216.0f рівний (int) 16777217, (подвійний) 16777217.0, і те, і інше? Порівняння між інтегральними типами є тонкими, але порівняння з плаваючою комою слід проводити лише ІМХО зі значеннями, явно віднесеними до відповідних типів. Порівняння a floatз чимось іншим, ніж a float, або a doubleз чимось іншим, ніж a double, вражає мене як головний запах коду, який не повинен складатись без діагностики.
supercat

1
@supercat Я згоден - це неприємно, що x == yне означає x/3 == y/3(спробуйте x = 5і y = 5.0).
Полковник Паніка

Я вважаю, що використання /для цілого поділу є дефектом у дизайні C # та Java. Паскаль divі навіть VB.NET ` are much better. The problems with == `гірші, хоча: x==yі y==zце не означає x==z(розглянемо три числа в моєму попередньому коментарі). Що ж стосується ставлення ви пропонуєте, навіть якщо xі yобидва floatабо обидва double, x.equals((Object)y)не означає , що 1.0f/x == 1.0f / y` (якщо у мене були druthers, це буде гарантувати , що, навіть якщо ==не розрізняє позитивні і нуль, Equalsповинен).
supercat

Це нормально, тому що першим параметром рівняння () є рядок!
Whiplash

17

Наскільки я розумію, відповідь проста:

  1. == порівнює посилання на об'єкти.
  2. .Equals порівнює об’єктний вміст.
  3. String типи даних завжди діють як порівняння контенту.

Я сподіваюся, що я правильно і що він відповів на ваше запитання.


15

Я додам, що якщо ви кинете об'єкт на рядок, він буде працювати правильно. Ось чому компілятор видасть вам попередження:

Можливе ненавмисне порівняння посилань; щоб порівняти значення, наведіть ліву частину набрати "рядок"


1
Саме так. @DominicCronin: Завжди дотримуйтесь попереджень часу компіляції. Якщо у вас є object expr = XXX; if (expr == "Energy") { ... }, то оскільки лівий бік має тип компіляції object, компілятор повинен використовувати перевантаження operator ==(object, object). Він перевіряє рівність еталону. Чи це буде давати trueчи falseможе бути важко передбачити через строге інтернування . Якщо ви знаєте, що ліва частина є nullабо типу string, або тип , stringперед використанням використовуйте ліву частину ==.
Jeppe Stig Nielsen

сказати частину цього іншого способу. == (у визначенні того, що він використовує опорну рівність або рівність значення) залежить від типу часу компіляції / статичного типу / типу лівої сторони. (це тип, який вирішується в аналізі часу компіляції). Замість типу часу виконання / динамічного типу / типу RHS. Код BlueMonkMN показує, що хоч і не з кастингом.
барлоп

5

Оскільки статична версія .Equalметоду досі не згадувалася, я хотів би додати це сюди, щоб узагальнити та порівняти 3 варіації.

MyString.Equals("Somestring"))          //Method 1
MyString == "Somestring"                //Method 2
String.Equals("Somestring", MyString);  //Method 3 (static String.Equals method) - better

де MyStringє змінна, яка надходить десь у коді.

Довідкова інформація та літо:

У Java, що використовує ==для порівняння рядки, не слід використовувати. Я згадую про це, якщо вам потрібно використовувати обидві мови, а також повідомляти про це== також можна замінити чимось кращим у C #.

У C # немає практичної різниці для порівняння рядків за допомогою методу 1 або методу 2 до тих пір, поки обидва мають рядковий тип. Однак, якщо нуль, інший тип (наприклад, ціле число) або один представляє об'єкт, який має іншу посилання, то, як показує початкове запитання, ви можете відчути, що порівняння вмісту для рівності може не повернути те, що ви очікуєте

Пропоноване рішення:

Оскільки використання ==не зовсім таке, як використання .Equalsпри порівнянні речей, ви можете використовувати статичний метод String.Equals замість цього. Таким чином, якщо дві сторони не є одним типом, ви все одно будете порівнювати вміст, і якщо одна недійсна, ви уникнете винятку.

   bool areEqual = String.Equals("Somestring", MyString);  

Трохи більше писати, але, на мій погляд, безпечніше використовувати.

Ось інформація, скопійована з Microsoft:

public static bool Equals (string a, string b);

Параметри

a Рядок

Перший рядок для порівняння, або null.

b Рядок

Другий рядок для порівняння, або null.

Повертається Boolean

trueякщо значення aтаке ж, як значення b; в іншому випадку false. Якщо обидва aі bє null, метод повертається true.


5

Як додаток до вже хороших відповідей: така поведінка НЕ ​​обмежується рядками або порівнянням різних типів чисел. Навіть якщо обидва елементи є об'єктом типу одного і того ж базового типу. "==" не працюватиме.

На наступному скріншоті показані результати порівняння двох об'єктних значень

Приклад з VS2017


2

Я тут трохи розгублений. Якщо тип виконання вмісту має рядковий тип, то обидва == і рівне повинні повернути true. Однак, оскільки, схоже, це не так, то тип вмісту під час виконання не є рядковим, а виклик рівних на ньому робить референтну рівність, і це пояснює, чому рівне ("енергетична атака") не вдається. Однак у другому випадку рішення про те, до якого слід викликати перевантажений == статичний оператор, приймається під час компіляції, і це рішення є == (рядок, рядок). це говорить про те, що вміст забезпечує неявне перетворення в рядок.


2
У вас це назад на фронт. Для початку рівний ("Energy Attack") не провалюється, == - це той, який повертає помилково. == виходить з ладу, оскільки він використовує == від об'єкта, а не рядка.
MikeKulls

За замовчуванням оператор == тестує на рівність еталону, визначаючи, чи вказують два посилання на один і той же об'єкт. Тому типи довідок не повинні реалізовувати operator == для того, щоб отримати цю функціональність. Коли тип є незмінним, тобто дані, що містяться в екземплярі, неможливо змінити, перевантажуючий оператор == для порівняння рівності значення замість еталонної рівності може бути корисним, оскільки як незмінні об'єкти їх можна вважати такими ж, як довгі оскільки вони мають однакове значення. Недоцільно переосмислити оператор == у непорушних типах.
Wajeed-MSFT

2

Є ще один вимір попередньої відповіді від @BlueMonkMN. Додатковий вимір полягає в тому, що відповідь на запитання назви @ Drahcir, як це зазначено, також залежить від того, як ми дійшли до stringзначення. Проілюструвати:

string s1 = "test";
string s2 = "test";
string s3 = "test1".Substring(0, 4);
object s4 = s3;
string s5 = "te" + "st";
object s6 = s5;
Console.WriteLine("{0} {1} {2}", object.ReferenceEquals(s1, s2), s1 == s2, s1.Equals(s2));

Console.WriteLine("\n  Case1 - A method changes the value:");
Console.WriteLine("{0} {1} {2}", object.ReferenceEquals(s1, s3), s1 == s3, s1.Equals(s3));
Console.WriteLine("{0} {1} {2}", object.ReferenceEquals(s1, s4), s1 == s4, s1.Equals(s4));

Console.WriteLine("\n  Case2 - Having only literals allows to arrive at a literal:");
Console.WriteLine("{0} {1} {2}", object.ReferenceEquals(s1, s5), s1 == s5, s1.Equals(s5));
Console.WriteLine("{0} {1} {2}", object.ReferenceEquals(s1, s6), s1 == s6, s1.Equals(s6));

Вихід:

True True True

  Case1 - A method changes the value:
False True True
False False True

  Case2 - Having only literals allows to arrive at a literal:
True True True
True True True

2

Додавання ще однієї точки до відповіді.

.EqualsTo() метод дає вам можливість порівняти культуру та залежність від регістру.


0

==Маркер в C # використовується для двох різних операторів рівності-перевірки. Коли компілятор зіткнеться з цим маркером, він перевірить, чи один із типів, що порівнюються, реалізував перевантаження оператора рівності для конкретних типів комбінації, що порівнюється (*), або для комбінації типів, до яких обидва типи можуть бути перетворені. Якщо компілятор знайде таке перевантаження, він використовуватиме його. В іншому випадку, якщо обидва типи є обома еталонними типами і вони не пов'язані між собою класами (або це може бути інтерфейс, або вони можуть бути пов'язаними класами), компілятор буде розглядатись ==як оператор порівняння посилань. Якщо не застосовується жодна умова, компіляція не вдасться.

Зауважте, що деякі інші мови використовують окремі маркери для двох операторів перевірки рівності. Наприклад, у VB.NET =маркер використовується у виразах виключно для оператора перевірки рівності, який можна завантажити, і Isвикористовується як оператор еталонного тестування або нульового тестування. Використання =типу, що не переосмислює оператор перевірки рівності, не вдасться, як і спроби використанняIs для будь-якої іншої мети, крім тестування еталонної рівності чи недійсності.

(*) Типи, як правило, лише перевантажують рівність для порівняння з собою, але може бути корисним для типів перевантажувати оператор рівності для порівняння з іншими конкретними типами; наприклад, intміг (і IMHO мав би, але не) визначив операторів рівності для порівняння float, щоб 16777217 не повідомив про себе рівним 16777216f. Оскільки це не, оскільки жоден такий оператор не визначений, C # просуне intдо float, округлюючи його до 16777216f, перш ніж оператор перевірки рівності бачить його; Потім оператор бачить два рівні числа з плаваючою комою і повідомляє про них як про рівних, не знаючи про округлення, що відбулося.


Замість того, щоб повернення порівняння "int-to-float" повертало помилкове, я віддаю перевагу підходу, який використовує F #, який полягає у тому, щоб взагалі заборонити таке порівняння. Тоді програміст може вирішити, чи потрібно поводитися з тим, що значення мають різний тип. Тому що іноді, в кінці кінців, ми робимо хочемо лікувати 3як рівний 3.0f. Якщо ми вимагаємо від програміста сказати те, що призначено у кожному випадку, тоді немає небезпеки поведінки за замовчуванням, що призведе до непередбачуваних результатів, оскільки немає поведінки за замовчуванням.
foog

@phoog: Моє особисте відчуття полягає в тому, що мови повинні мати свої "нормальні" засоби тестування рівності, застосовувати відношення еквівалентності та забороняти всі комбінації операндів, для яких це не було б. Я не бачу величезної переваги в наявності мовної перевірки рівності між цілими числами і плавцями, підтверджуючи, що float точно представляє ціле число, що відповідає int, а просто просто забороняє такі порівняння, але вважає, що будь-який підхід перевершує виконання мови конвертація збитків перед порівнянням.
supercat

0

Дійсно чудові відповіді та приклади!

Я просто хотів би додати принципову різницю між ними,

Такі оператори, як ==не є поліморфними, поки Equalsє

Маючи на увазі цю концепцію, якщо ви працюєте з будь-яким прикладом (дивлячись на тип посилання лівої та правої руки та перевіряючи / знаючи, чи справді тип перевантажений == оператор перевантажений і рівне, що його перекрито), ви впевнені, щоб отримати правильну відповідь .


-1

Коли ми створюємо будь-який об’єкт, до об'єкта є дві частини, одна - це вміст, а інша - посилання на цей вміст. ==порівнює як зміст, так і посилання; equals()порівнює лише зміст

http://www.codeproject.com/Articles/584128/What-is-the-difference-between-equalsequals-and-Eq


1
Це не правда. Якщо aі bє обома посиланнями рядків, то результат a == bне залежить від того, вказують посилання на один і той же об'єкт.
фог

-2

==

Оператор == може використовуватися для порівняння двох змінних будь-якого типу, і він просто порівнює біти .

int a = 3;
byte b = 3;
if (a == b) { // true }

Примітка: на лівій стороні int є більше нулів, але нас це не хвилює.

int a (00000011) == байт b (00000011)

Пам'ятайте, що оператор == піклується лише про візерунок бітів змінної.

Використовуйте == Якщо два посилання (примітиви) посилаються на один і той же об'єкт у купі.

Правила однакові, незалежно від того, чи є змінна референтною чи примітивною.

Foo a = new Foo();
Foo b = new Foo();
Foo c = a;

if (a == b) { // false }
if (a == c) { // true }
if (b == c) { // false }

a == c вірно a == b - помилково

візерунок бітів однаковий для a і c, тому вони рівні, використовуючи ==.

Дорівнює ():

Використовуйте метод equals (), щоб побачити, чи однакові два різні об'єкти .

Наприклад, два різних об'єкти String, які обоє представляють символів у "Джейн"


2
Це неправильно. Розглянемо наступне: object a = 3; object b = 3; Console.WriteLine(a == b);. Вихід є помилковим, навіть якщо бітові шаблони значень однакові. Мають значення і типи операндів. Причиною, що нам "байдуже" щодо різної кількості нулів у вашому прикладі, є те, що до моменту виклику оператора рівняння кількість нулів насправді однакове через неявне перетворення.
фог

-2

Єдина різниця між рівними та == полягає у порівнянні типів об’єктів. в інших випадках, таких як еталонні типи та типи значень, вони майже однакові (або обидва є бітовою рівністю, або обидва є еталонною рівністю).

об'єкт: Дорівнює: біт-розумна рівність ==: опорна рівність

рядок: (дорівнює і == однакові для рядка, але якщо одна з рядків змінилася на об'єкт, результат порівняння буде іншим) Дорівнює: біт-розумна рівність ==: біт-мудра рівність

Дивіться тут для отримання додаткових пояснень.


Object.Equals не обов'язково дивиться на розрядну рівність. Це віртуальний метод, і переопрацювання може робити все, що завгодно.
фог

так, ти маєш рацію, ти можеш робити все, що хочеш, щоб перекрити це. але тема, про яку ми говоримо, - це реалізація за замовчуванням. реалізація за замовчуванням Object.Equals - це бітова мудрість.
Буде Ю.
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.