string.Equals()和==运算符是否真的一样?
他们真的一样吗? 今天,我遇到了这个问题。 以下是立即窗口的转储:
?s "Category" ?tvi.Header "Category" ?s == tvi.Header false ?s.Equals(tvi.Header) true ?s == tvi.Header.ToString() true
所以, s
和tvi.Header
都包含“Category”,但==
返回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
-
== == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == ==
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
的例子(从Joseph Albahari的注释到C#语言规范的第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)
。 这些方法确定两个字符串是否是相同的character-for-character,这很少是正确的行为。 最好使用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#有两个“等于”的概念: Equals
和ReferenceEquals
。 对于大多数你会遇到的类, ==
运算符使用一个或另一个(或两者),并且通常只在处理引用类型时测试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)
TreeViewItem
的Header
属性被静态类型化为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
除了Jon Skeet的回答之外 ,我想解释为什么大多数情况下使用==
的时候,实际上在具有相同值的不同字符串实例上得到了答案:
string a = "Hell"; string b = "Hello"; a = a + "o"; Console.WriteLine(a == b);
正如你所看到的, a
和b
必须是不同的字符串实例,但是因为字符串是不可变的,所以运行时使用所谓的字符串interning来让a
和b
在内存中引用相同的字符串。 对象的==
运算符检查引用,并且由于a
和b
引用同一个实例,所以结果为true
。 当你改变其中任何一个时,一个新的字符串实例被创建,这就是为什么字符串实际上是可能的。
顺便说一下,Jon Skeet的答案并不完整。 的确, x == y
是false
但那只是因为他比较对象和对象是通过引用来比较的。 如果你写(string)x == (string)y
,它会再次返回true
。 所以字符串有== – 运算符重载,它调用下面的String.Equals
。
这里有很多描述性的答案,所以我不会重复已经说过的话。 我想补充的是下面的代码,展示了我能想到的所有排列。 由于组合的数量,代码相当长。 随意把它放到MSTest中,看看自己的输出(输出包含在底部)。
这个证据支持Jon Skeet的答案。
码:
[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
很明显, tvi.header
不是一个String
。 ==
是一个由String
类重载的运算符,这意味着只有在编译器知道运算符的两边都是String
它才会工作。
一个对象由一个唯一的OBJECT_ID定义。 如果A和B是对象而A == B是真的,那么它们是相同的对象,它们具有相同的数据和方法,但是,这也是正确的:
A.OBJECT_ID == B.OBJECT_ID
如果A.Equals(B)是真的,那就意味着两个物体处于相同的状态,但这并不意味着A与B是一样的。
字符串是对象。
请注意==和Equals运算符是自反的,simetric,tranzitive,所以它们是等价关系(使用关系代数术语)
这意味着:如果A,B和C是对象,那么:
(1)A == A总是如此; 答案(A)总是如此(反身性)
(2)如果A == B,则B == A; 如果A.Equals(B)then 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
现实生活中的例子:两个相同类型的汉堡具有相同的属性:它们是汉堡类的对象,它们的属性完全相同,但它们是不同的实体。 如果你买这两个汉堡,吃一个,另一个不会吃。 所以,Equals和==之间的区别:你有hamburger1和hamburger2。 它们完全处于相同的状态(相同的重量,相同的温度,相同的味道),所以hamburger1.Equals(hamburger2)是正确的。 但是hamburger1 == hamburger2是错误的,因为如果汉堡包1的状态发生变化,汉堡包2的状态不一定会改变,反之亦然。
如果你和一个朋友在同一时间得到一个汉堡,那么你必须决定把汉堡分成两部分,因为you.getHamburger()== friend.getHamburger()是真的,如果发生这种情况:friend.eatHamburger(),那么你的汉堡也会被吃掉。
我可以写关于Equals和==的其他细节,但是我越来越饿了,所以我必须去。
最好的问候,拉霍斯Arpad。