C # разница между == и Equals ()

548

У меня есть условие в приложении silverlight, которое сравнивает 2 строки, по какой-то причине, когда я использую ==его, возвращает false, а .Equals()возвращает true .

Вот код:

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

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

Есть причина, почему это происходит?

Drahcir
источник
2
См. Также: stackoverflow.com/questions/144530/or-equals
Стрелка
8
Строка переопределяет ==, но операторы не являются полиморфными. В этом коде ==оператор вызывается для типа object, который выполняет сравнение идентификаторов вместо значения один.
Дрю Ноакс
12
Чтобы расширить комментарий @DrewNoakes: Компилятор выбирает ==перегрузку на основе типа операндов во время компиляции. ContentСвойство object. Операторы не являются виртуальными, поэтому ==вызывается реализация по умолчанию для сравнения ссылок. С Equals вызов переходит к виртуальному методу object.Equals(object); stringпереопределяет этот метод и выполняет порядковое сравнение содержимого строки. См. Msdn.microsoft.com/en-us/library/fkfd9eh8(v=vs.110).aspx и referenceource.microsoft.com/#mscorlib/system/string.cs,507 .
Фог
6
Объяснение @ Phoog является точным. Следует отметить, что когда левая часть ==имеет тип времени компиляции, objectа правая часть имеет тип времени компиляции string, то компилятор C # должен выбрать (в данном случае проблематично) перегрузку operator ==(object, object); но он будет выдавать предупреждение во время компиляции , что это может быть НЕПРЕДВИДЕННЫМ. Так что читайте предупреждения во время компиляции! Чтобы исправить проблему и по-прежнему использовать ==, приведите левую сторону к string. Если я правильно помню, текст предупреждения предлагает именно это.
Джепп Стиг Нильсен
1
@JeppeStigNielsen +1 за совет, чтобы прочитать предупреждения компилятора. Еще лучше: включите опцию предупреждений как ошибок, чтобы заставить всех обращать на них внимание.
фото

Ответы:

429

Когда ==используется в выражении типа object, оно разрешается в System.Object.ReferenceEquals.

Equalsявляется просто virtualметодом и ведет себя как таковой, поэтому будет использоваться переопределенная версия (которая для stringтипа сравнивает содержимое).

Мехрдад Афшари
источник
57
Если только оператор специально не реализован в классе
Доминик Кронин
23
@DominicCronin Это не правда. Даже если == реализован в классе, он будет проигнорирован, потому что тип слева от сравнения - объект. Похоже, что перегрузки операторов определяются во время компиляции, и во время компиляции все, что он знает, это то, что левая часть является объектом.
MikeKulls
4
@DominicCronin Я считаю, что ваше первое утверждение верно в том смысле, что == будет разрешать объект, но ваше второе утверждение о том, что перегрузки операторов разрешаются аналогичным образом, - нет. Они совершенно разные, поэтому .Equals будет разрешать строку, а == будет разрешать объект.
MikeKulls
8
Чтобы было ясно, objectтип (обратите внимание на моноширинный шрифт) технически подразумевается как «выражение типа System.Object». Он не имеет ничего общего с типом времени выполнения экземпляра, на который ссылается выражение. Я думаю, что утверждение «пользовательские операторы обрабатываются как virtualметоды» чрезвычайно вводит в заблуждение. Они обрабатываются как перегруженные методы и зависят только от типа операндов во время компиляции. Фактически, после вычисления набора пользовательских операторов-кандидатов, оставшаяся часть процедуры привязки будет точно алгоритмом разрешения перегрузки метода
Mehrdad Afshari
4
@DominicCronin Вводящая в заблуждение часть заключается в том, что virtualразрешение метода зависит от фактического типа времени выполнения экземпляра, в то время как это полностью игнорируется в разрешении перегрузки оператора, и это действительно весь смысл моего ответа.
Мехрдад Афшари
314

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

Обычно (когда не имеет дело со строками), Equalsсравнивает значения , а ==сравнивает ссылки на объекты . Если два объекта, которые вы сравниваете, ссылаются на один и тот же точный экземпляр объекта, то оба будут возвращать true, но если один имеет одинаковое содержимое и поступил из другого источника (это отдельный экземпляр с одинаковыми данными), только 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
BlueMonkMN
источник
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в коде equals для устранения неоднозначности.

Эрик Липперт недавно сделал сообщение в блоге на тему, почему в CLR есть 2 метода равенства. Стоит прочитать

JaredPar
источник
Ну, Джаред, ты прямо нарушаешь знаменитое высказывание Джеффа: «Лучший код - это вообще не код» Это действительно оправдано? С другой стороны, я могу видеть, откуда это происходит и почему было бы желательно сделать семантику явной. В этом случае я очень предпочитаю, чтобы VB работал с объектным равенством. Это коротко и однозначно.
Конрад Рудольф
@ Конрад, я действительно должен был сказать «когда я незнаком с типом, я считаю, что лучшая практика заключается в следующем». Да, здесь у VB гораздо лучшая семантика, потому что она действительно разделяет равенство значений и ссылок. C # смешивает их вместе, и это иногда вызывает ошибки неоднозначности.
JaredPar
10
Это не совсем правда. == не может быть переопределено, это статический метод. Это может быть только перегружено, что является важным отличием. Таким образом, код, который выполняется для оператора ==, связан во время компиляции, в то время как Equals является виртуальным и находится во время выполнения.
Стефан Штайнеггер
20

== Оператор

  1. Если операнды являются типами значений и их значения равны, он возвращает true, иначе false.
  2. Если операнды являются ссылочными типами, за исключением строки, и оба ссылаются на один и тот же экземпляр (один и тот же объект), он возвращает true, иначе false.
  3. Если операнды имеют строковый тип и их значения равны, он возвращает true, иначе false.

.equals

  1. Если операнды являются ссылочными типами , он выполняет эталонное равенство, то есть если оба ссылаются на один и тот же экземпляр (один и тот же объект), он возвращает true, иначе false.
  2. Если операнды являются типами значений, то в отличие от оператора == он сначала проверяет их тип, а если их типы совпадают, он выполняет оператор ==, в противном случае он возвращает false.
Kashif
источник
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, (double) 16777217.0, обоим или ни одному? Сравнения между целочисленными типами хороши, но сравнения с плавающей точкой должны выполняться только IMHO со значениями, которые явно приводятся к совпадающим типам. Сравнение a floatс чем-то, отличным от a float, или a doubleс чем-то, отличным от a double, кажется мне основным запахом кода, который не должен компилироваться без диагностики.
суперкат
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` (если бы у меня были мои барабанщики, это гарантировало бы это; даже если ==не различать положительное и ноль, Equalsследует)
суперкат
Это нормально, потому что первый параметр Equals () - это строка!
Хлыст
17

Насколько я понимаю, ответ прост:

  1. == сравнивает ссылки на объекты.
  2. .Equals сравнивает содержимое объекта.
  3. String Типы данных всегда действуют как сравнение содержимого.

Надеюсь я прав и что он ответил на ваш вопрос.

Лираз Шака Амир
источник
15

Я бы добавил, что если вы приведете ваш объект к строке, то он будет работать правильно. Вот почему компилятор выдаст вам предупреждение:

Возможное непреднамеренное сравнение ссылок; чтобы получить сравнение значений, приведите левую сторону к типу 'строка'

MikeKulls
источник
1
Точно. @DominicCronin: всегда соблюдайте предупреждения во время компиляции. Если да object expr = XXX; if (expr == "Energy") { ... }, то, поскольку левая часть имеет тип времени компиляции object, компилятор должен использовать перегрузку operator ==(object, object). Проверяет равенство ссылок. Даст ли это trueили falseможет быть трудно предсказать из-за интернирования строк . Если вы знаете, что левая сторона имеет nullтип или тип string, stringперед использованием используйте левую сторону ==.
Джепп Стиг Нильсен
поставить часть этого по-другому. == (при определении, использует ли он ссылочное равенство или равенство значений), зависит от типа времени компиляции / статического типа / типа левой стороны. (это тип, который разрешается в анализе времени компиляции). Вместо типа времени выполнения / динамического типа / типа 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, если оба имеют тип string. Однако, если один имеет значение null, другой имеет другой тип (например, целое число) или один представляет объект, имеющий другую ссылку, то, как показывает первоначальный вопрос, вы можете столкнуться с тем, что сравнение содержимого на равенство может не дать того, что вы ожидаете.

Предлагаемое решение:

Поскольку использование ==- это не то же самое, что использование .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

Просто как дополнение к и без того хорошим ответам: это поведение не ограничивается строками или сравнением различных числовых типов. Даже если оба элемента имеют тип объекта одного и того же базового типа. "==" не сработает.

На следующем снимке экрана показаны результаты сравнения двух значений объекта {int}.

Пример из VS2017

Оле Альберс
источник
2

Я немного смущен здесь. Если тип времени выполнения Content имеет тип string, то оба == и Equals должны возвращать true. Однако, поскольку это не так, то тип контента во время выполнения не является строкой, и вызов Equals для него делает ссылочное равенство, и это объясняет, почему Equals («энергетическая атака») завершается неудачно. Однако во втором случае решение о том, какой перегруженный == статический оператор должен быть вызван, принимается во время компиляции, и это решение выглядит как == (строка, строка). это говорит мне о том, что Content обеспечивает неявное преобразование в строку.

Мехмет Арас
источник
2
У вас есть это обратно на фронт. Для начала Equals («энергетическая атака») не провалится, == это тот, который возвращает false. Сбой ==, потому что он использует == из объекта, а не строки.
MikeKulls
По умолчанию оператор == проверяет равенство ссылок, определяя, указывают ли две ссылки на один и тот же объект. Следовательно, ссылочные типы не должны реализовывать оператор == для получения этой функциональности. Когда тип является неизменяемым, то есть данные, содержащиеся в экземпляре, не могут быть изменены, может быть полезен оператор перегрузки == для сравнения равенства значений вместо ссылочного равенства, поскольку в качестве неизменяемых объектов их можно считать одинаковыми как long так как они имеют одинаковое значение. Не стоит переопределять оператор == в неизменяемых типах.
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
Novichok
источник
2

Добавив еще один пункт к ответу.

.EqualsTo() метод дает вам возможность сравнить с культурой и чувствительностью к регистру.

Bala
источник
0

==Маркер в C # используется для двух разных операторов равенства-проверки. Когда компилятор обнаруживает этот токен, он проверяет, реализован ли в одном из сравниваемых типов перегрузка оператора равенства для сравниваемых типов конкретных комбинаций (*) или для комбинации типов, в которую можно преобразовать оба типа. Если компилятор обнаружит такую ​​перегрузку, он будет использовать ее. В противном случае, если оба типа являются ссылочными типами и не являются несвязанными классами (могут быть интерфейсом или связанными классами), компилятор будет рассматривать их ==как оператор сравнения ссылок. Если ни одно из условий не применимо, компиляция не удастся.

Обратите внимание, что некоторые другие языки используют отдельные токены для двух операторов проверки равенства. Например, в VB.NET =токен используется в выражениях исключительно для перегружаемого оператора проверки равенства и Isиспользуется в качестве оператора reference-test или null-test. Использовать =для типа, который не переопределяет оператор проверки на равенство, не удастся, так же как и для попытки использовать его Isдля любой цели, кроме проверки ссылочного равенства или недействительности.

(*) Типы обычно только перегружают равенство для сравнения с самим собой, но может быть полезно, чтобы типы перегружали оператор равенства для сравнения с другими конкретными типами; например, intмог бы (и ИМХО должен был, но не сделал) определить операторы равенства для сравнения с floatтаким образом, чтобы 16777217 не сообщал о себе равным 16777216f. Как таковой, так как такой оператор не определен, C # будет повышать intto float, округляя его до 16777216f до того, как оператор проверки равенства увидит его; Затем этот оператор видит два одинаковых числа с плавающей точкой и сообщает о них как о равных, не подозревая о том, что имело место округление.

Supercat
источник
Вместо того, чтобы сравнение int-to-float возвращало false, я предпочитаю подход, который использует F #, который вообще запрещает такое сравнение. Затем программист может решить, следует ли и как обрабатывать тот факт, что значения имеют другой тип. Потому что иногда, в конце концов, мы делаем хотим лечить 3как равный 3.0f. Если мы требуем, чтобы программист говорил, что предназначено в каждом случае, тогда нет опасности поведения по умолчанию, которое приведет к непредвиденным результатам, так как поведение по умолчанию отсутствует.
Фог
@phoog: Мое личное мнение состоит в том, что языки должны иметь свои «нормальные» средства тестирования на равенство, реализующие отношение эквивалентности и запрещающие любые комбинации операндов, для которых это не так. Я не вижу огромного преимущества наличия проверки языка на равенство между целыми числами и числами с плавающей точкой, подтверждая, что число с плавающей точкой точно представляет целое число, совпадающее с целым числом, по сравнению с простым запрещением таких сравнений, но я бы посчитал, что любой подход лучше, чем выполнение языка преобразование с потерями перед сравнением.
суперкат
0

Действительно отличные ответы и примеры!

Я просто хотел бы добавить фундаментальную разницу между ними,

Операторы, такие как ==не являются полиморфными, в то время Equalsкак

Имея это в виду, если вы разработаете какой-либо пример (взглянув на левый и правый ссылочный тип и проверив / узнав, действительно ли в типе перегружен оператор == и равно ли он переопределен), вы наверняка получите правильный ответ. ,

Маниш Басантани
источник
-1

Когда мы создаем какой-либо объект, объект состоит из двух частей, одна из которых является контентом, а другая - ссылкой на этот контент. ==сравнивает как содержание, так и ссылку; equals()сравнивает только контент

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

user3440463
источник
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, которые оба представляют символы в «Джейн»

Sanchit
источник
2
Это неверно Рассмотрим следующее: object a = 3; object b = 3; Console.WriteLine(a == b);. Вывод ложен, хотя битовые комбинации значений одинаковы. Типы операндов также имеют значение. Причина, по которой мы «не заботимся» о различном количестве нулей в вашем примере, заключается в том, что к тому моменту, когда мы вызываем оператор равенства, число нулей фактически одинаково из-за неявного преобразования.
Фог
-2

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

объект: равно: побитовое равенство ==: ссылочное равенство

строка: (равно и == одинаковы для строки, но если одна из строк изменена на объект, результат сравнения будет другим) Равно: побитовое равенство ==: побитовое равенство

Смотрите здесь для более подробного объяснения.

Будет ю
источник
Object.Equals не обязательно смотрит на битовое равенство. Это виртуальный метод, и переопределение может делать все, что захочет.
Фог
да, вы правы, вы можете сделать все, что хотите, чтобы переопределить это. но тема, о которой мы говорим, является реализацией по умолчанию. реализация Object.Equals по умолчанию - побитовое равенство.
Будет ли Ю