Are string.Equals() and == operator really same? [

2018-12-31 15:19发布

问题:

This question already has an answer here:

  • C# difference between == and Equals() 16 answers

Are they really same? Today, I ran into this problem. Here is the dump from the Immediate Window:

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

So, both s and tvi.Header contain \"Category\", but == returns false and Equals() returns true.

s is defined as string, tvi.Header is actually a WPF TreeViewItem.Header. So, why are they returning different results? I always thought that they were interchangable in C#.

Can anybody explain why this is?

回答1:

Two differences:

  • Equals is polymorphic (i.e. it can be overridden, and the implementation used will depend on the execution-time type of the target object), whereas the implementation of == used is determined based on the compile-time types of the objects:

    // 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 will go bang if you call it on null, == won\'t

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

Note that you can avoid the latter being a problem using object.Equals:

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


回答2:

The apparent contradictions that appear in the question are caused because in one case the Equals function is called on a string object, and in the other case the == operator is called on the System.Object type. string and object implement equality differently from each other (value vs. reference respectively).

Beyond this fact, any type can define == and Equals differently, so in general they are not interchangeable.

Here’s an example using double (from Joseph Albahari’s note to §7.9.2 of the C# language specification):

double x = double.NaN;
Console.WriteLine (x == x);         // False
Console.WriteLine (x != x);         // True
Console.WriteLine (x.Equals(x));    // True

He goes on to say that the double.Equals(double) method was designed to work correctly with lists and dictionaries. The == operator, on the other hand, was designed to follow the IEEE 754 standard for floating point types.

In the specific case of determining string equality, the industry preference is to use neither == nor string.Equals(string) most of the time. These methods determine whether two string are the same character-for-character, which is rarely the correct behavior. It is better to use string.Equals(string, StringComparison), which allows you to specify a particular type of comparison. By using the correct comparison, you can avoid a lot of potential (very hard to diagnose) bugs.

Here’s one example:

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

Both strings in this example look the same (\"Café\"), so this could be very tough to debug if using a naïve (ordinal) equality.



回答3:

C# has two \"equals\" concepts: Equals and ReferenceEquals. For most classes you will encounter, the == operator uses one or the other (or both), and generally only tests for ReferenceEquals when handling reference types (but the string Class is an instance where C# already knows how to test for value equality).

  • Equals compares values. (Even though two separate int variables don\'t exist in the same spot in memory, they can still contain the same value.)
  • ReferenceEquals compares the reference and returns whether the operands point to the same object in memory.

Example 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)


回答4:

The Header property of the TreeViewItem is statically typed to be of type object.

Therefore the == yields false. You can reproduce this with the following simple snippet:

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


回答5:

In addition to Jon Skeet\'s answer, I\'d like to explain why most of the time when using == you actually get the answer true on different string instances with the same value:

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

As you can see, a and b must be different string instances, but because strings are immutable, the runtime uses so called string interning to let both a and b reference the same string in memory. The == operator for objects checks reference, and since both a and b reference the same instance, the result is true. When you change either one of them, a new string instance is created, which is why string interning is possible.

By the way, Jon Skeet\'s answer is not complete. Indeed, x == y is false but that is only because he is comparing objects and objects compare by reference. If you\'d write (string)x == (string)y, it will return true again. So strings have their ==-operator overloaded, which calls String.Equals underneath.



回答6:

There are plenty of descriptive answers here so I\'m not going to repeat what has already been said. What I would like to add is the following code demonstrating all the permutations I can think of. The code is quite long due to the number of combinations. Feel free to drop it into MSTest and see the output for yourself (the output is included at the bottom).

This evidence supports Jon Skeet\'s answer.

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;
}

Output:

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


回答7:

It is clear that tvi.header is not a String. The == is an operator that is overloaded by String class, which means it will be working only if compiler knows that both side of the operator are String.



回答8:

An object is defined by an OBJECT_ID, which is unique. If A and B are objects and A == B is true, then they are the very same object, they have the same data and methods, but, this is also true:

A.OBJECT_ID == B.OBJECT_ID

if A.Equals(B) is true, that means that the two objects are in the same state, but this doesn\'t mean that A is the very same as B.

Strings are objects.

Note that the == and Equals operators are reflexive, simetric, tranzitive, so they are equivalentic relations (to use relational algebraic terms)

What this means: If A, B and C are objects, then:

(1) A == A is always true; A.Equals(A) is always true (reflexivity)

(2) if A == B then B == A; If A.Equals(B) then B.Equals(A) (simetry)

(3) if A == B and B == C, then A == C; if A.Equals(B) and B.Equals(C) then A.Equals(C) (tranzitivity)

Also, you can note that this is also true:

(A == B) => (A.Equals(B)), but the inverse is not true.

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

Example of real life: Two Hamburgers of the same type have the same properties: they are objects of the Hamburger class, their properties are exactly the same, but they are different entities. If you buy these two Hamburgers and eat one, the other one won\'t be eaten. So, the difference between Equals and ==: You have hamburger1 and hamburger2. They are exactly in the same state (the same weight, the same temperature, the same taste), so hamburger1.Equals(hamburger2) is true. But hamburger1 == hamburger2 is false, because if the state of hamburger1 changes, the state of hamburger2 not necessarily change and vice versa.

If you and a friend get a Hamburger, which is yours and his in the same time, then you must decide to split the Hamburger into two parts, because you.getHamburger() == friend.getHamburger() is true and if this happens: friend.eatHamburger(), then your Hamburger will be eaten too.

I could write other nuances about Equals and ==, but I\'m getting hungry, so I have to go.

Best regards, Lajos Arpad.



标签: c# string