string.Equals()和==运算符是否真的相同?

时间:2010-09-09 17:00:05

标签: c# string

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

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

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

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

有人可以解释为什么会这样吗?

8 个答案:

答案 0 :(得分:339)

两个不同之处:

  • 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
    
  • 如果你在null上调用它,
  • 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

答案 1 :(得分:57)

问题中出现的明显矛盾是由于在一种情况下在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é”),因此如果使用天真(序数)相等,这可能非常难以调试。

答案 2 :(得分:44)

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)

答案 3 :(得分:16)

TreeViewItem的{​​{3}}属性被静态输入为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

答案 4 :(得分:4)

Jon Skeet's answer之外,我想解释一下为什么在使用==时大部分时间你实际上在具有相同值的不同字符串实例上得到答案true:< / p>

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

如您所见,ab必须是不同的字符串实例,但由于字符串是不可变的,因此运行时使用所谓的string interning来同时让ab在内存中引用相同的字符串。对象==运算符检查引用,由于ab引用同一个实例,结果为true。当您更改其中任何一个时,都会创建一个新的字符串实例,这就是可以进行字符串实习的原因。

顺便说一句,Jon Skeet的回答并不完整。实际上,x == yfalse,但这仅仅是因为他通过引用比较对象和对象。如果您要写(string)x == (string)y,它将再次返回true。所以字符串的== - 运算符重载,调用下面的String.Equals

答案 5 :(得分:4)

这里有很多描述性答案,所以我不打算重复已经说过的话。我想补充的是以下代码,展示了我能想到的所有排列。由于组合的数量,代码很长。随意将其放入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

答案 6 :(得分:3)

很明显,tvi.header不是String==是由String类重载的运算符,这意味着只有在编译器知道运算符的两边都是String时它才会起作用。

答案 7 :(得分:-1)

对象由OBJECT_ID定义,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.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)=&gt; (A.Equals(B)),但反之则不然。

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

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

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

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

祝你好运, Lajos Arpad。