Are string.Equals() and == operator really same?
它们真的一样吗?今天,我遇到了这个问题。下面是即时窗口中的转储:
1 2 3 4 5 6 7 8 9 10 | ?s "Category" ?tvi.Header "Category" ?s == tvi.Header false ?s.Equals(tvi.Header) true ?s == tvi.Header.ToString() true |
因此,
有人能解释这是为什么吗?
两个不同点:
Equals 是多态的(即它可以被重写,使用的实现将取决于目标对象的执行时间类型),而使用的== 的实现是根据对象的编译时间类型确定的:1
2
3
4
5
6
7
8
9
10
11
12
13
14// 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 将爆炸,==不会1
2
3
4
5
6string x = null;
string y = null;
if (x.Equals(y)) // Bang
if (x == y) // Yes
请注意,使用
1 | if (object.Equals(x, y)) // Fine even if x or y is null |
问题中出现的明显矛盾是由于在一种情况下,
除此之外,任何类型都可以以不同的方式定义
下面是一个使用
1 2 3 4 | double x = double.NaN; Console.WriteLine (x == x); // False Console.WriteLine (x != x); // True Console.WriteLine (x.Equals(x)); // True |
他接着说,
在确定字符串相等的特定情况下,行业偏好大多数时候既不使用
下面是一个例子:
1 2 3 4 5 | 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_"),所以如果使用na,调试起来可能非常困难。ve(序数)相等。
C有两个"相等"的概念:
Equals 比较数值。(即使两个单独的int 变量不存在于内存中的同一位置,它们仍然可以包含相同的值。)ReferenceEquals 比较引用并返回操作数是否指向内存中的同一对象。
示例代码:
1 2 3 4 5 6 7 8 9 10 | 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) |
因此,
1 2 3 4 5 6 7 |
这里有很多描述性的答案,所以我不会重复已经说过的话。我想添加的是下面的代码,演示我能想到的所有排列。由于组合的数量,代码相当长。您可以随意将其放入MSTEST,自己查看输出(输出包含在底部)。
这些证据支持乔恩·斯基特的回答。
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 | [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; } |
输出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | 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 |
除了jon skeet的答案外,我想解释一下为什么在使用
1 2 3 4 | string a ="Hell"; string b ="Hello"; a = a +"o"; Console.WriteLine(a == b); |
如您所见,
顺便说一下,乔恩·斯基特的回答不完整。的确,
很明显,
对象由唯一的对象ID定义。如果A和B是物体,并且A==B为真,则它们是非常相同的对象,它们具有相同的数据和方法,但这也是真的:
a.object_id==b.object_id
如果a.等于(b)是真的,这意味着两个物体处于相同的状态,但这并不意味着a和b是完全相同的。
字符串是对象。
注意,==和等于运算符是自反的、同余的、转换的,因此它们是等价关系(使用关系代数项)
这意味着:如果A、B和C是对象,则:
(1)a==a始终为真;a.equals(a)始终为真(自反性)
(2)如果a=b,则b=a;如果a.等于(b),则b.等于(a)(同时)
(3)如果a==b和b==c,则a==c;如果a.等于(b)和b.等于(c),则a.等于(c)(变率)
此外,您还可以注意到,这也是正确的:
(a==b)=>(a.等于(b)),但倒数不是真的。
1 2 3 4 5 | A B => 0 0 1 0 1 1 1 0 0 1 1 1 |
现实生活的例子:同一类型的两个汉堡具有相同的属性:它们是汉堡类的对象,它们的属性完全相同,但它们是不同的实体。如果你买这两个汉堡包,一个吃,另一个就不吃了。所以,等号和==之间的区别是:你有汉堡1和汉堡2。它们处于完全相同的状态(同样的重量,同样的温度,同样的味道),所以汉堡等于汉堡。但汉堡包1==汉堡包2是错误的,因为如果汉堡包1的状态发生变化,汉堡包2的状态就不一定发生变化,反之亦然。
如果你和一个朋友同时得到一个汉堡包,这是你和他的,那么你必须决定把汉堡包分成两部分,因为你.getHamburger()==friend.getHamburger()是真的,如果发生这种情况:friend.eatHamburger(),那么你的汉堡包也会被吃掉。
我可以写一些关于平等和==的细微差别,但我饿了,所以我得走了。
最好的问候,Lajos Arpad。