Sont-ils vraiment pareils? Aujourd'hui, j'ai rencontré ce problème. Voici le dump de la fenêtre immédiate:
?s
"Category"
?tvi.Header
"Category"
?s == tvi.Header
false
?s.Equals(tvi.Header)
true
?s == tvi.Header.ToString()
true
Ainsi, s
et tvi.Header
contiennent tous deux "Catégorie", mais ==
renvoie false et Equals()
renvoie true.
s
est défini en tant que chaîne, tvi.Header
est en fait un WPF TreeViewItem.Header
. Alors, pourquoi retournent-ils des résultats différents? J'ai toujours pensé qu'ils étaient interchangeables en C #.
Quelqu'un peut-il expliquer pourquoi?
Deux différences:
Equals
est polymorphe (c.-à-d. Qu'il peut être remplacé et que l'implémentation utilisée dépendra du type d'exécution de l'objet cible), alors que l'implémentation de ==
utilisé est déterminée en fonction de compile-time types des objets:
// 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
ira bang si vous l'appelez sur null, == ne le fera pas
string x = null;
string y = null;
if (x.Equals(y)) // Bang
if (x == y) // Yes
Notez que vous pouvez éviter que ce dernier ne pose problème à l’aide de object.Equals
:
if (object.Equals(x, y)) // Fine even if x or y is null
Les contradictions apparentes apparaissant dans la question sont dues au fait que dans un cas, la fonction Equals
est appelée sur un objet string
, et dans l'autre cas, l'opérateur ==
est appelé dans le System.Object
type. string
et object
implémentent différemment l'égalité (valeur ou référence).
Au-delà de ce fait, tout type peut définir ==
et Equals
différemment, donc, en général, ils ne sont pas interchangeables.
Voici un exemple utilisant double
(extrait de la note de Joseph Albahari au §7.9.2 de la spécification du langage C #):
double x = double.NaN;
Console.WriteLine (x == x); // False
Console.WriteLine (x != x); // True
Console.WriteLine (x.Equals(x)); // True
Il ajoute que la méthode double.Equals(double)
a été conçue pour fonctionner correctement avec les listes et les dictionnaires. L'opérateur ==
, quant à lui, a été conçu pour suivre la norme IEEE 754 pour les types à virgule flottante.
Dans le cas spécifique de la détermination de l'égalité de chaîne, la préférence du secteur est de n'utiliser ni ==
ni string.Equals(string)
la plupart du temps. Ces méthodes déterminent si deux chaînes sont identiques caractère par caractère, ce qui est rarement le comportement correct. Il est préférable d'utiliser string.Equals(string, StringComparison)
, qui vous permet de spécifier un type de comparaison particulier. En utilisant la comparaison correcte, vous pouvez éviter beaucoup de bogues potentiels (très difficiles à diagnostiquer).
Voici un exemple:
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
Les deux chaînes de cet exemple se ressemblent ("Café"). Cela peut donc être très difficile à résoudre si vous utilisez une égalité naïve (ordinale).
C # a deux concepts "égaux": Equals
et ReferenceEquals
. Pour la plupart des classes que vous rencontrerez, l'opérateur ==
utilise l'une ou l'autre (ou les deux), et ne teste généralement que pour ReferenceEquals
lors du traitement des types de référence (mais string
Class est une instance où C # sait déjà comment tester l’égalité des valeurs).
Equals
compare les valeurs. (Même si deux variables int
distinctes n'existent pas au même endroit en mémoire, elles peuvent toujours contenir la même valeur.)ReferenceEquals
compare la référence et indique si les opérandes pointent vers le même objet en mémoire.Exemple de code:
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)
La propriété Header
de TreeViewItem
est de type statique et doit être de type object
.
Par conséquent, le ==
donne false
. Vous pouvez reproduire cela avec l'extrait simple suivant:
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
En plus de réponse de Jon Skeet , je voudrais expliquer pourquoi, la plupart du temps, lorsque vous utilisez ==
, vous obtenez réellement la réponse true
sur différentes instances de chaîne avec la même valeur. :
string a = "Hell";
string b = "Hello";
a = a + "o";
Console.WriteLine(a == b);
Comme vous pouvez le constater, a
et b
doivent être des instances de chaînes différentes, mais comme les chaînes sont immuables, le moteur d'exécution utilise ce que l'on appelle string interning pour laisser les deux a
et b
font référence à la même chaîne en mémoire. L'opérateur ==
pour les objets vérifie la référence, et puisque a
et b
font référence à la même instance, le résultat est true
. Lorsque vous modifiez l'un ou l'autre, une nouvelle instance de chaîne est créée. C'est pourquoi il est possible d'interner des chaînes.
En passant, la réponse de Jon Skeet n’est pas complète. En effet, x == y
est false
mais c'est uniquement parce qu'il compare des objets et que les objets se comparent par référence. Si vous écrivez (string)x == (string)y
, il retournera true
à nouveau. Ainsi, les chaînes ont leur opérateur == - surchargé, qui appelle String.Equals
en dessous.
Il y a beaucoup de réponses descriptives ici, donc je ne vais pas répéter ce qui a déjà été dit. Ce que je voudrais ajouter, c’est le code suivant illustrant toutes les permutations auxquelles je peux penser. Le code est assez long en raison du nombre de combinaisons. N'hésitez pas à le déposer dans MSTest et à voir le résultat vous-même (le résultat est inclus en bas).
Cette preuve appuie la réponse de Jon Skeet.
Code:
[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;
}
Sortie:
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
Il est clair que tvi.header
n'est pas un String
. Le ==
est un opérateur surchargé par la classe String
, ce qui signifie qu'il ne fonctionnera que si le compilateur sait que les deux côtés de l'opérateur sont String
.
Un objet est défini par un OBJECT_ID unique. Si A et B sont des objets et que A == B est vrai, alors ils sont exactement le même objet, ils ont les mêmes données et méthodes, mais ceci est également vrai:
A.OBJECT_ID == B.OBJECT_ID
si A.Equals (B) est vrai, cela signifie que les deux objets sont dans le même état, mais cela ne signifie pas que A est identique à B.
Les chaînes sont des objets.
Notez que les opérateurs == et Equals sont réflexifs, simétriques, tranzitifs, ils sont donc des relations équivalentes (utiliser des termes algébriques relationnels)
Ce que cela signifie: Si A, B et C sont des objets, alors:
(1) A == A est toujours vrai; A.Equals (A) est toujours vrai (réflexivité)
(2) si A == B alors B == A; Si A.Equals (B), alors B.Equals (A) (simulation)
(3) si A == B et B == C, alors A == C; si A.Equals (B) et B.Equals (C), alors A.Equals (C) (transparence)
En outre, vous pouvez noter que cela est également vrai:
(A == B) => (A.Equals (B)), mais l'inverse n'est pas vrai.
A B =>
0 0 1
0 1 1
1 0 0
1 1 1
Exemple de la vie réelle: Deux Hamburgers du même type ont les mêmes propriétés: ce sont des objets de la classe Hamburger, leurs propriétés sont exactement les mêmes, mais ce sont des entités différentes. Si vous achetez ces deux Hamburgers et en mangez un, l'autre ne sera pas mangé. Donc, la différence entre Equals et ==: vous avez hamburger1 et hamburger2. Ils sont exactement dans le même état (même poids, même température, même goût), donc hamburger1.Equals (hamburger2) est vrai. Mais hamburger1 == hamburger2 est faux, car si l’état d’hamburger1 change, l’état d’hamburger2 ne change pas nécessairement et inversement.
Si vous et un ami obtenez un Hamburger, qui est à la fois le sien et le sien, vous devez alors décider de scinder le Hamburger en deux parties, car you.getHamburger () == friend.getHamburger () est vrai et si cela se produit. : friend.eatHamburger (), votre Hamburger sera également mangé.
Je pourrais écrire d'autres nuances sur Equals et ==, mais je commence à avoir faim, alors je dois y aller.
Cordialement, Lajos Arpad.