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

Они действительно такие же? Сегодня я столкнулся с этой проблемой. Вот дамп из окна 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. Итак, почему они возвращают разные результаты? Я всегда думал, что они были взаимозаменяемы на С#.

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

Ответ 1

Две отличия:

  • 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

Ответ 2

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

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

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

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, 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é" ), поэтому это может быть очень сложно отладить при использовании наивного (порядкового) равенства.

Ответ 3

С# имеет два понятия "равно": Equals и ReferenceEquals. Для большинства классов, с которыми вы столкнетесь, оператор == использует тот или иной (или оба), и обычно тестирует только ReferenceEquals при обработке ссылочных типов (но класс string - это экземпляр, где С# уже знает, как тест для равенства значений).

  • 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)

Ответ 4

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

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

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

Ответ 5

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

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

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

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

Ответ 6

Здесь есть много описательных ответов, поэтому я не буду повторять то, что уже было сказано. Я хотел бы добавить следующий код, демонстрирующий все перестановки, о которых я могу думать. Код довольно длинный из-за количества комбинаций. Не стесняйтесь бросать его в 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<T1, T2, T3>(Expression<Func<T1, T2, T3>> tryFunc, T1 in1, T2 in2)
{
    T3 out1;

    Try(tryFunc, e => { }, in1, in2, out out1);
}
public bool Try<T1, T2, T3>(Expression<Func<T1, T2, T3>> tryFunc, Action<Exception> 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

Ответ 7

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

Ответ 8

Объект определяется идентификатором 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)), но обратное неверно.

A B =>
0 0 1
0 1 1
1 0 0
1 1 1

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

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

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

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