string.Equals()和==运算符真的一样吗?

它们真的一样吗?今天,我遇到了这个问题。下面是立即窗口的转储:

?s
"Category"
?tvi.Header
"Category"
?s == tvi.Header
false
?s.Equals(tvi.Header)
true
?s == tvi.Header.ToString()
true

因此,stvi.Header都包含“Category”,但==返回false, Equals()返回true。

s被定义为字符串,tvi.Header实际上是WPF的TreeViewItem.Header。那么,为什么它们返回不同的结果呢?我一直认为它们在c#中是可以互换的。

有人能解释一下这是为什么吗?

272706 次浏览

两个差异:

  • 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将抛出一个异常,如果你在null时调用它,==不会

      string x = null;
    string y = null;
    
    
    if (x.Equals(y)) // NullReferenceException
    
    
    if (x == y) // Yes
    

注意,你可以使用object.Equals来避免后一个问题:

if (object.Equals(x, y)) // Fine even if x or y is null

c#有两个“等号”概念:EqualsReferenceEquals。对于你将遇到的大多数类,==操作符使用其中一个或另一个(或两者都使用),并且通常只在处理引用类型时测试ReferenceEquals(但string类是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)

TreeViewItemHeader属性静态类型为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

除了乔恩·斯基特的回答之外,我还想解释为什么在使用==时,大多数情况下你实际上会在具有相同值的不同字符串实例上得到true:

string a = "Hell";
string b = "Hello";
a = a + "o";
Console.WriteLine(a == b);

正如你所看到的,ab必须是不同的字符串实例,但因为字符串是不可变的,所以运行时使用所谓的字符串实习ab在内存中引用相同的字符串。对象的==操作符检查引用,由于ab都引用同一个实例,所以结果是true。当您更改其中任何一个时,将创建一个新的字符串实例,这就是为什么字符串实习是可能的。

顺便说一下,乔恩·斯基特的回答并不完整。的确,x == yfalse,但这只是因为他在比较对象,而对象是通过引用比较的。如果你写(string)x == (string)y,它会再次返回true。所以字符串重载了它们的==-操作符,在下面调用String.Equals

问题中出现的明显矛盾是因为在一种情况下,Equals函数是在string对象上调用的,而在另一种情况下,==操作符是在System.Object类型上调用的。stringobject实现的相等不同(分别是值和引用)。

除此之外,任何类型都可以以不同的方式定义==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)。这些方法确定两个字符串是否是相同的字符对字符,这很少是正确的行为。最好使用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é"),所以如果使用naïve(序数)相等,调试起来可能会非常困难。

对象由唯一的OBJECT_ID定义。如果A和B是对象 A == B为真,那么它们是相同的对象,它们有相同的数据和方法,但是,这也是真的:

A.object_id == b.object_id

< p >如果 A. equals (B)为真,这意味着两个对象处于相同的状态,但这并不意味着A与B完全相同

字符串是对象。

请注意==和Equals操作符是自反的、simetric的、tranzitive的,因此它们是等价关系(使用关系代数术语)。

这意味着: 如果A, B和C是对象,则:

(1) A == A总是正确的;A. equals (A)总是正确的(反身性)

(2)如果A == B那么B == A;如果A等于(B)那么B等于(A) (simetry)

(3)如果A == B且B == C,则A == C;如果a .等于(B), B.等于(C),则a .等于(C)(静止性)

另外,你可以注意到这也是真的:

(A == B) => (A = (B)),但反过来就不对了。

A B =>
0 0 1
0 1 1
1 0 0
1 1 1

现实生活的例子: 相同类型的两个汉堡包具有相同的属性:它们是Hamburger类的对象,它们的属性完全相同,但它们是不同的实体。如果你买了这两个汉堡,吃了一个,另一个就不会被吃掉了。因此,Equals和==之间的区别是: 你有汉堡1和汉堡2。它们完全处于相同的状态(相同的重量,相同的温度,相同的味道),所以hamburger1.Equals(hamburger2)是正确的。但是hamburger1 == hamburger2为false,因为如果hamburger1的状态改变了,hamburger2的状态不一定会改变,反之亦然

如果你和一个朋友同时得到一个汉堡,这是你的和他的,那么你必须决定把汉堡分成两部分,因为你gethamburger () == friend. gethamburger()为真,如果这种情况发生:friend. eathamburger(),那么你的汉堡也会被吃掉。

我可以写关于Equals和==的其他细微差别,但我有点饿了,所以我得走了。

< p >最诚挚的问候, Lajos亚珥拔。< / p >

很明显,tvi.header不是String==是一个由String类重载的操作符,这意味着只有当编译器知道操作符的两边都是String时,它才会工作。

这里有很多描述性的答案,所以我不打算重复已经说过的内容。我想添加的是下面的代码,演示了我能想到的所有排列。由于组合的数量,代码相当长。您可以将其放到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