Операторы string.Equals () и == действительно одинаковы?

Действительно ли они такие же? Сегодня я столкнулся с этой проблемой. Вот дамп из windows Immediate:

?s "Category" ?tvi.Header "Category" ?s == tvi.Header false ?s.Equals(tvi.Header) true ?s == tvi.Header.ToString() true 

Таким образом, и s и tvi.Header содержат «Категория», но == возвращает false, а Equals() возвращает true.

s определяется как строка, tvi.Header на самом деле является WPF TreeViewItem.Header . Итак, почему они возвращают разные результаты? Я всегда думал, что они были взаимозаменяемы на C #.

Может ли кто-нибудь объяснить, почему это так?

Два отличия:

  • Equals является полиморфным (т. Е. Его можно переопределить, а используемая реализация будет зависеть от типа времени выполнения целевого объекта), тогда как реализация == используется на основе типов времени компиляции объектов:

     // Avoid getting confused by interning object x = new StringBuilder("hello").ToString(); object y = new StringBuilder("hello").ToString(); if (x.Equals(y)) // Yes // The compiler doesn't know to call ==(string, string) so it generates // a reference comparision instead if (x == y) // No string xs = (string) x; string ys = (string) y; // Now *this* will call ==(string, string), comparing values appropriately if (xs == ys) // Yes 
  • Equals будут биться, если вы назовёте его нулевым, == не будет

     string x = null; string y = null; if (x.Equals(y)) // Bang if (x == y) // Yes 

Обратите внимание, что вы можете избежать проблемы с использованием object.Equals :

 if (object.Equals(x, y)) // Fine even if x or y is null 

Очевидные противоречия, возникающие в вопросе, вызваны тем, что в одном случае функция Equals вызывается для string объекта, а в другом случае оператор == вызывается в типе System.Object . string и object реализуют равенство по-разному друг от друга (значение против ссылки соответственно).

Помимо этого факта, любой тип может определять == и Equals разному, поэтому в целом они не являются взаимозаменяемыми.

Вот пример использования double (от примечания Джозефа Альбахари к §7.9.2 спецификации языка C #):

 double x = double.NaN; Console.WriteLine (x == x); // False Console.WriteLine (x != x); // True Console.WriteLine (x.Equals(x)); // True 

Далее он говорит, что метод double.Equals(double) был разработан для правильной работы со списками и словарями. Оператор == , с другой стороны, был разработан для соответствия стандарту IEEE 754 для типов с плавающей точкой.

В конкретном случае определения равенства строк предпочтение в отрасли заключается в том, что большую часть времени string.Equals(string) использовать ни == или string.Equals(string) . Эти методы определяют, являются ли две строки одинаковыми символами для символов, что редко приводит к правильному поведению. Лучше использовать string.Equals(string, StringComparison) , который позволяет вам указать конкретный тип сравнения. Используя правильное сравнение, вы можете избежать большого количества ошибок (очень трудно диагностировать).

Вот один пример:

 string one = "Caf\u00e9"; // U+00E9 LATIN SMALL LETTER E WITH ACUTE string two = "Cafe\u0301"; // U+0301 COMBINING ACUTE ACCENT Console.WriteLine(one == two); // False Console.WriteLine(one.Equals(two)); // False Console.WriteLine(one.Equals(two, StringComparison.InvariantCulture)); // True 

Обе строки в этом примере выглядят одинаково («Café»), поэтому это может быть очень сложно отлаживать, если использовать наивное (порядковое) равенство.

C # имеет два понятия «равно»: равные и ReferenceEquals . Для большинства classов, с которыми вы столкнетесь, оператор == использует тот или иной (или оба), и обычно это только тесты для ReferenceEquals при обработке ссылочных типов (но string Class – это экземпляр, где C # уже знает, как проверить равенство значений) ,

  • Equals сравнивает значения. (Несмотря на то, что две отдельные переменные int не существуют в одном и том же месте в памяти, они все равно могут содержать одно и то же значение.)
  • ReferenceEquals сравнивает ссылку и возвращает, указывают ли операнды на один и тот же объект в памяти.

Пример кода:

 var s1 = new StringBuilder("str"); var s2 = new StringBuilder("str"); StringBuilder sNull = null; s1.Equals(s2); // True object.ReferenceEquals(s1, s2); // False s1 == s2 // True - it calls Equals within operator overload s1 == sNull // False object.ReferenceEquals(s1, sNull); // False s1.Equals(sNull); // Nono! Explode (Exception) 

Свойство Header TreeViewItem статически типизировано как object типа.

Поэтому == возвращает false . Вы можете воспроизвести это с помощью следующего простого fragmentа:

 object s1 = "Hallo"; // don't use a string literal to avoid interning string s2 = new string(new char[] { 'H', 'a', 'l', 'l', 'o' }); bool equals = s1 == s2; // equals is false equals = string.Equals(s1, s2); // equals is true 

В дополнение к ответу Джона Скита я хотел бы объяснить, почему большую часть времени, когда вы используете == вы действительно получаете ответ true для разных экземпляров строк с одинаковым значением:

 string a = "Hell"; string b = "Hello"; a = a + "o"; Console.WriteLine(a == b); 

Как вы можете видеть, a и b должны быть разными строковыми экземплярами, но поскольку строки неизменяемы, среда выполнения использует так называемую интерполяцию строк, чтобы и a и b ссылались на одну и ту же строку в памяти. Оператор == для объектов проверяет ссылку, и поскольку и a и b ссылаются на один и тот же экземпляр, результат является true . Когда вы меняете один из них, создается новый экземпляр строки, поэтому возможно интернирование строк.

Кстати, ответ Джона Скита не завершен. Действительно, x == y является false но это только потому, что он сравнивает объекты и объекты по ссылке. Если вы напишете (string)x == (string)y , он вернет true раз. Таким образом, строки имеют перегруженный оператор == -, который вызывает String.Equals под ним.

Здесь есть много описательных ответов, поэтому я не буду повторять сказанное. Я хотел бы добавить следующий код, демонстрирующий все перестановки, о которых я могу думать. Код довольно длинный из-за количества комбинаций. Не стесняйтесь бросать его в MSTest и видеть результат для себя (выход включен в нижней части).

Это доказательство подтверждает ответ Джона Скита.

Код:

 [TestMethod] public void StringEqualsMethodVsOperator() { string s1 = new StringBuilder("string").ToString(); string s2 = new StringBuilder("string").ToString(); Debug.WriteLine("string a = \"string\";"); Debug.WriteLine("string b = \"string\";"); TryAllStringComparisons(s1, s2); s1 = null; s2 = null; Debug.WriteLine(string.Join(string.Empty, Enumerable.Repeat("-", 20))); Debug.WriteLine(string.Empty); Debug.WriteLine("string a = null;"); Debug.WriteLine("string b = null;"); TryAllStringComparisons(s1, s2); } private void TryAllStringComparisons(string s1, string s2) { Debug.WriteLine(string.Empty); Debug.WriteLine("-- string.Equals --"); Debug.WriteLine(string.Empty); Try((a, b) => string.Equals(a, b), s1, s2); Try((a, b) => string.Equals((object)a, b), s1, s2); Try((a, b) => string.Equals(a, (object)b), s1, s2); Try((a, b) => string.Equals((object)a, (object)b), s1, s2); Debug.WriteLine(string.Empty); Debug.WriteLine("-- object.Equals --"); Debug.WriteLine(string.Empty); Try((a, b) => object.Equals(a, b), s1, s2); Try((a, b) => object.Equals((object)a, b), s1, s2); Try((a, b) => object.Equals(a, (object)b), s1, s2); Try((a, b) => object.Equals((object)a, (object)b), s1, s2); Debug.WriteLine(string.Empty); Debug.WriteLine("-- a.Equals(b) --"); Debug.WriteLine(string.Empty); Try((a, b) => a.Equals(b), s1, s2); Try((a, b) => a.Equals((object)b), s1, s2); Try((a, b) => ((object)a).Equals(b), s1, s2); Try((a, b) => ((object)a).Equals((object)b), s1, s2); Debug.WriteLine(string.Empty); Debug.WriteLine("-- a == b --"); Debug.WriteLine(string.Empty); Try((a, b) => a == b, s1, s2); #pragma warning disable 252 Try((a, b) => (object)a == b, s1, s2); #pragma warning restore 252 #pragma warning disable 253 Try((a, b) => a == (object)b, s1, s2); #pragma warning restore 253 Try((a, b) => (object)a == (object)b, s1, s2); } public void Try(Expression> tryFunc, T1 in1, T2 in2) { T3 out1; Try(tryFunc, e => { }, in1, in2, out out1); } public bool Try(Expression> tryFunc, Action catchFunc, T1 in1, T2 in2, out T3 out1) { bool success = true; out1 = default(T3); try { out1 = tryFunc.Compile()(in1, in2); Debug.WriteLine("{0}: {1}", tryFunc.Body.ToString(), out1); } catch (Exception ex) { Debug.WriteLine("{0}: {1} - {2}", tryFunc.Body.ToString(), ex.GetType().ToString(), ex.Message); success = false; catchFunc(ex); } return success; } 

Вывод:

 string a = "string"; string b = "string"; -- string.Equals -- Equals(a, b): True Equals(Convert(a), b): True Equals(a, Convert(b)): True Equals(Convert(a), Convert(b)): True -- object.Equals -- Equals(a, b): True Equals(Convert(a), b): True Equals(a, Convert(b)): True Equals(Convert(a), Convert(b)): True -- a.Equals(b) -- a.Equals(b): True a.Equals(Convert(b)): True Convert(a).Equals(b): True Convert(a).Equals(Convert(b)): True -- a == b -- (a == b): True (Convert(a) == b): False (a == Convert(b)): False (Convert(a) == Convert(b)): False -------------------- string a = null; string b = null; -- string.Equals -- Equals(a, b): True Equals(Convert(a), b): True Equals(a, Convert(b)): True Equals(Convert(a), Convert(b)): True -- object.Equals -- Equals(a, b): True Equals(Convert(a), b): True Equals(a, Convert(b)): True Equals(Convert(a), Convert(b)): True -- a.Equals(b) -- a.Equals(b): System.NullReferenceException - Object reference not set to an instance of an object. a.Equals(Convert(b)): System.NullReferenceException - Object reference not set to an instance of an object. Convert(a).Equals(b): System.NullReferenceException - Object reference not set to an instance of an object. Convert(a).Equals(Convert(b)): System.NullReferenceException - Object reference not set to an instance of an object. -- a == b -- (a == b): True (Convert(a) == b): True (a == Convert(b)): True (Convert(a) == Convert(b)): True 

Понятно, что tvi.header не является String . == – это оператор, который перегружен classом String , что означает, что он будет работать только в том случае, если компилятор знает, что обе стороны оператора – это String .

Объект определяется идентификатором OBJECT_ID, который является уникальным. Если A и B являются объектами и A == B истинно, то они являются одним и тем же объектом, они имеют одни и те же данные и методы, но это также верно:

A.OBJECT_ID == B.OBJECT_ID

если A.Equals (B) истинно, это означает, что оба объекта находятся в одном состоянии, но это не означает, что A является тем же самым, что B.

Строки – это объекты.

Обратите внимание, что операторы == и Equals являются рефлексивными, симетрическими, транзитными, поэтому они являются эквивалентными отношениями (для использования реляционных алгебраических терминов)

Это означает: если A, B и C являются объектами, тогда:

(1) A == A всегда истинно; A.Equals (A) всегда истинно (рефлексивность)

(2) если A == B, то B == A; Если A.Equals (B), то B.Equals (A) (simetry)

(3) если A == B и B == C, то A == C; если A.Equals (B) и B.Equals (C), то A.Equals (C) (tranzitivity)

Кроме того, вы можете заметить, что это также верно:

(A == B) => (A.Equals (B)), но обратное неверно.

 AB => 0 0 1 0 1 1 1 0 0 1 1 1 

Пример реальной жизни: два гамбургера одного типа имеют одинаковые свойства: они являются объектами classа гамбургеров, их свойства точно такие же, но они разные. Если вы купите этих двух гамбургеров и съедите один, другой не будет съеден. Итак, разница между Equals и ==: у вас есть гамбургер1 и гамбургер2. Они находятся в одном и том же состоянии (одинаковый вес, одна и та же температура, тот же вкус), поэтому hamburger1.Equals (hamburger2) верен. Но гамбургер1 == hamburger2 является ложным, потому что если состояние гамбургера1 меняется, состояние гамбургера2 не обязательно меняется и наоборот.

Если вы и друг получите гамбургер, который принадлежит вам и ему в одно и то же время, тогда вы должны решить разбить гамбургер на две части, потому что you.getHamburger () == friend.getHamburger () истинно, и если это произойдет : friend.eatHamburger (), тогда ваш гамбургер тоже будет съеден.

Я мог бы написать другие нюансы о Equals и ==, но я проголодался, поэтому мне нужно идти.

С уважением, Лайош Арпад.

  • Проверка наличия строкового массива, и если да, то его позиция
  • Извлечь цифры из строки в Java
  • Преобразование строки в GregorianCalendar
  • Как выполнить команду для каждой строки файла?
  • Как преобразовать массив объектов в строковый массив в Java
  • Строки C-Style в качестве аргументов шаблона?
  • Как преобразовать String в long в Java?
  • Scala String vs java.lang.String - вывод типа
  • Простой способ удалить несколько пробелов в строке?
  • Сравните одну строку с несколькими значениями в одном выражении
  • Как нарисовать линию направленной стрелки в Java?
  • Давайте будем гением компьютера.