Разница между оператором == и методом Equals() в С#?

В чем разница между == и Equals() с примером? Я знаю, что == используется для сравнения оператора, а метод Equals() используется для сравнения содержимого строки. Так я пробовал

// first example
string s1 = "a";
string s2 = "a";
Console.Write(a.Equals(s2)); // returns true, but if I assign "b" to s2,
                             // then result will be false

// second example
string s1 ="a";
string s2 ="a";
Console.Write(s1 == s2);     // returns true

Как это так? Оба - разные ссылки на объекты. Предположим, мы считаем, что это ссылки. Но я старался использовать это как

string s1 = new string("ab");
string s2 = new string("ab");

Я получаю ошибку времени компиляции, которая не может преобразовать строку в char

Ответ 1

Происходит несколько вещей. Во-первых, в этом примере:

string s1 = "a";
string s2 = "a";
Console.WriteLine(s1 == s2);

Вы утверждаете, что:

Оба являются разными объектами.

Это неверно из-за интернирования строк. s1 и s2 являются ссылками на один и тот же объект. Спецификация С# гарантирует, что - из раздела 2.4.4.5 спецификации С# 4:

Когда два или более строковых литерала, эквивалентные в соответствии с оператором равенства строк (§7.10.7), отображаются в одной и той же программе, эти строковые литералы относятся к одному и тому же экземпляру строки.

Итак, в этом конкретном случае вы все равно получите "true", даже если вы напечатали object.ReferenceEquals(s1, s2), или если вы использовали его для сравнения подлинности ссылки с ==:

object s1 = "a";
object s2 = "a";
Console.WriteLine(s1 == s2); // Still prints True due to string literal interning

Однако, даже если это ссылки на отдельные объекты, == перегружается для string. Перегрузка - это решение, используемое во время компиляции. Реализация для использования зависит от типов экземпляров для компиляции. Так, например:

string a = new string('x', 1);
string b = new string('x', 1);
Console.WriteLine(a == b); // Uses string implementation, prints True

object c = a;
object d = b;
Console.WriteLine(c == d); // Reference identity comparison, prints False

Сравните это с object.Equals(object), который является виртуальным методом. Как это бывает, string также перегружает этот метод, но, тем не менее, он переопределяет его. Поэтому, если мы изменим наш код на:

string a = new string('x', 1);
string b = new string('x', 1);
Console.WriteLine(a.Equals((object) b));

object c = a;
object d = b;
Console.WriteLine(c.Equals(d));

... тогда оба вызова метода в скомпилированном коде просто будут object.Equals(object), но они все равно оба будут печатать True из-за полиморфизма: будет использоваться реализация в string.

Вот как выглядит вызов перегруженного метода:

string a = new string('x', 1);
string b = new string('x', 1);
Console.WriteLine(a.Equals(b)); // Calls string.Equals(string)

Ответ 2

Цитата из документации Равные:

Стандартная реализация Equals поддерживает ссылочное равенство для ссылочные типы и побитовое равенство для типов значений. Справка равенство означает, что ссылки на объекты, которые сравниваются, относятся к тот же объект. Поразрядное равенство означает, что объекты, которые сравниваются, то же двоичное представление.

И оператор ==:

Для предопределенных типов значений оператор равенства (==) возвращает true, если значения его операндов равны, в противном случае - false. Для справки типы, отличные от string, == возвращает true, если его два операнда ссылаются на тот же объект. Для типа строки == сравнивает значения строки.

Теперь вернемся к вашему вопросу: почему s1 == s2 возвращает true? Строки - это специальные животные в .NET. Они представляют собой неизменяемые ссылочные типы. Они интернированы в .NET. Это означает, что если у вас есть две строковые константы с одинаковым значением, они будут ссылаться на один и тот же экземпляр объекта во время выполнения.

Цитата из документации:

Общая среда выполнения в режиме реального времени сохраняет хранилище строк, поддерживая table, называемый междоменным пулом, который содержит единственную ссылку на каждая уникальная литеральная строка, объявленная или созданная программно в вашей программы. Следовательно, экземпляр литеральной строки с конкретное значение существует только один раз в системе.

Ответ 3

Вы думаете, что Java-esque. В java оператор == не может быть настроен, поэтому для ссылочных типов он всегда означает ссылочное равенство, тогда как это означает равенство значений для примитивных типов. С другой стороны, Equals предназначен для проверки равенства значений в ссылочных типах.

Тем не менее, в С# все по-другому. Оба Equals и == могут иметь пользовательские реализации. Разница в том, что Equals - это виртуальный (экземпляр) метод, а operator== - статический метод. Кроме того, они могут вести себя точно так же.

По умолчанию оба Equals и == проверяют ссылочное равенство для ссылочных типов и равенство значений для типов значений. Однако для string обе настроены для проверки равенства значений.