Операторы 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 и ==, но я проголодался, поэтому мне нужно идти.
С уважением,
Лайош Арпад.