241

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?

Jim Aho
  • 9,932
  • 15
  • 56
  • 87
miliu
  • 2,499
  • 2
  • 17
  • 7
  • I think string.Equals matches the whole object – Umair A. Sep 09 '10 at 17:01
  • 1
    Yes, I get the same result from inside my code. Actually, it brought to my attention because == returns false in the code. I always use == for string comparison. This morning I couldn't believe == returns false when I see both sides contain the same string "Category" to my eye (I even asked my co-worker to double-check if I see something wrong). But it works fine when I changed it to use Equals (same result as shown in Immediate Window). – miliu Sep 09 '10 at 17:12
  • 1
    You should consider calling the `string.Equals` overloads that include a `StringComparison` parameter for most string comparisons. Use the InvarientCulture versions for coded strings (XML attributes for example) or CurrentCulture for user-entered strings. This will take care of a lot of details that == ignores, such as Unicode character normalization forms, etc., and it makes case sensitivity explicit. – Jeffrey L Whitledge Sep 09 '10 at 18:07
  • 8
    @Robaticus, I don't understand what you are talking about. – miliu Sep 09 '10 at 18:17
  • The .Equals method looks at the contents of the string, the == operator compares the objects. == will only return true if they actually are the same object. This can get tricky with internment, in that if you have string a = "test" and string b = "test" it will actually be the same object and return true in an == comparison. – Kyle Oct 14 '10 at 12:50
  • 1
    == only works if the static type of the objects is string. Since operator overloading only takes the static type into consideration. – CodesInChaos Oct 14 '10 at 21:32

8 Answers8

368

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 throw an exception if you call it on null, == won't

      string x = null;
      string y = null;
    
      if (x.Equals(y)) // NullReferenceException
    
      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
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Doesn't == also include a Object.ReferenceEquals check for a quick `true` if they are the same object? – James Curran Sep 09 '10 at 17:09
  • @James: I'd expect that for both implementations, to be honest. But I haven't checked. – Jon Skeet Sep 09 '10 at 17:17
  • 4
    `x == y` equals false because you are checking reference equality with the object class' equality operator. `(string)x == (string)y` does in fact return true, in .Net 4.0 at least. – Daniel A.A. Pelsmaeker Sep 09 '10 at 17:33
  • @Virtlink: Exactly; it's not polymorphic. I've edited the answer to make that more obvious. – Jon Skeet Sep 09 '10 at 17:39
  • It's true that the `==` operator is not polymorphic; however, if `==` calls any virtual methods within its implementation (and many do simply by delegating to the `Equals` method) then it could also exhibit polymorphic behavior. – LBushkin Sep 09 '10 at 18:20
  • @Michael And this is why there was no Y2K bug in **any** legacy code. ;^D (aka, "I rolled scores on my 2600 games in base 10" aka ["Not everyone understands magic numbers"](http://en.wikipedia.org/wiki/Hexspeak#Notable_magic_numbers)) – ruffin Mar 07 '12 at 14:02
  • 4
    Great answer. Wouldn't it make more sense to use `String.Equals(x, y)` instead of `object.Equals(x, y)` to avoid the null issue? – Chaulky Jun 04 '13 at 18:29
  • @Chaulky: How would that avoid the null issue any more than `object.Equals` does? The only benefit is that it restricts both values to be of type `string`. – Jon Skeet Jun 04 '13 at 18:37
  • 3
    @Jon Skeet I was referring to that type check as being the advantage over `object.Equals(x, y)`. The question was about string comparisons, so it seems the added type check would be the benefit to using `String.Equals()` over `object.Equals()`. Is there a reason avoiding that type check would be better? Also, thanks for responding to a comment on question over a year old! – Chaulky Jun 04 '13 at 18:43
  • 2
    @Chaulky: No particular reason - other than I know `object.Equals` is *always* available to avoid nullity concerns, whereas for specific types I'd need to check the documentation :) – Jon Skeet Jun 04 '13 at 19:36
63

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.

Jeffrey L Whitledge
  • 58,241
  • 9
  • 71
  • 99
45

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)
Johnny_D
  • 4,592
  • 3
  • 33
  • 63
palswim
  • 11,856
  • 6
  • 53
  • 77
  • 1
    This is not quite true... Try compare two actual string objects with `==` that are different reference but same value, you will get `true`. Jon Skeet explains this... – Noldorin Sep 09 '10 at 22:28
  • 1
    @Noldorin: This is why I said: "The `==` operator has to choose between them sometimes, and **generally** chooses `ReferenceEquals` when handling nullable types." `string` is an example of a nullable type for which C# does not use only `ReferenceEquals`. The OP asked about `string` objects, but really needs to understand general C# objects. – palswim Sep 09 '10 at 22:39
  • +1 Indeed, and [that is what makes comparison in C#/.NET so very confusing and seemingly unspecified](http://stackoverflow.com/questions/112625/vs-object-equalsobject-in-net/3576334#3576334). – Dimitri C. Sep 10 '10 at 10:10
  • It doesn't have to choose between them, it can do something different instead. Not often a good idea, but it can. – Jon Hanna Oct 08 '10 at 00:03
  • @Jon Hanna: Good point; you *can* overload the `==` operator to do anything you want. – palswim Oct 08 '10 at 00:32
  • 1
    @palswim you wrote in your comment "string is an example of a nullable type"but nullable types are a subcategory of value types. Strings are reference types. See Section 1.3 of the spec – Conrad Frix Oct 13 '10 at 15:29
  • The main problem with this answer is the idea that there is one `==` operator that behaves differently depending on its operands. Because operators are *overloaded*, there are actually many `==` operators, and each one has whatever behavior its implementer has given it. The compiler chooses a particular operator depending on the compile-time types of the two operands. The rules governing that choice are described in section 7.3, Operators, of the C# spec version 5.0. – phoog Jan 08 '15 at 15:29
  • Another problem with this answer is that the example code does not compile -- for example, ReferenceEquals is not an instance method. Fixing those problems, however, also shows that the last line (`s1.Equals(sNull);`) does not in fact throw an exception, but rather evaluates to false. Why did you think it would throw an exception? – phoog Jan 08 '15 at 15:40
  • [Microsoft's Guidelines for Overriding the Equals and == Operator](https://msdn.microsoft.com/en-us/library/ms173147(v=vs.90).aspx) can offer some elucidation and further explanation as well. – palswim Feb 10 '16 at 19:16
17

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
Dirk Vollmar
  • 172,527
  • 53
  • 255
  • 316
  • Is this a WPF thing? I just started with WPF. I never ran into this kind of problems before in WinForm apps. So, we should always use Equals instead of ==? – miliu Sep 09 '10 at 17:36
  • 2
    It's worth noting that `s1 == s2` becomes `s1.Equals(s2)` IFF both s1 and s2 are declared as strings. String equality has special meaning in C#. – Jeff Mercado Sep 09 '10 at 17:39
  • @miliu: As you can see from my sample this is not related to WPF but generally the case in .NET. – Dirk Vollmar Sep 09 '10 at 18:10
  • I believe it's actually spelled "H-e-l-l-o" – Sergey Jun 16 '16 at 16:19
6

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.

Community
  • 1
  • 1
Daniel A.A. Pelsmaeker
  • 47,471
  • 20
  • 111
  • 157
  • 12
    I had thought that was obvious enough from the answer, but I've edited it to be more complete. However, your answer about interning is *not* correct. `a` and `b` refer to *different* instances above; string interning is only applied to compile-time constants. Your code prints True because it calls the == overload called, which is comparing the character sequences. The string that `a` ends up referring to has *not* been interned. – Jon Skeet Sep 09 '10 at 17:42
  • @Jon Skeet: Why does the string that `a` ends up referring to not interned? Is it because the compile-time value of `a` at the point of the comparison can not be determined? Would the initial `"Hell"` value be interned? – Ax. Sep 09 '10 at 18:30
  • 1
    @Ax: Yes - it's not a compile-time constant. "Hell" would indeed be interned. (And "Hello" would exist in the intern pool of course due to `b`; but `a` would be the result of calling `String.Concat("Hell", "o");` – Jon Skeet Sep 09 '10 at 20:54
  • 2
    @Ax.: The system only interns non-constant strings when explicitly requested to do so, in part because once a string has been interned, the interned copy can never be garbage-collected. If one happens to frequently use strings containing a particular sequence of 32,000 characters, interning the string may save 64K for each copy that would otherwise be created. But if one interns a 32,000 character string and never again uses that sequence of characters, one will permanently waste 64K of memory. Programs can't afford to do that too many times. – supercat Sep 23 '12 at 22:07
  • 3
    @Ax.: While it would perhaps be possible for the system to check, when creating the string, whether it's in the intern pool without trying to add it in the event it isn't, the vast majority of strings that get created in most programs aren't going to be in the intern pool. Checking every generated string against the intern pool on the off chance that a copy of it might be there would generally waste more time than it would save. – supercat Sep 23 '12 at 22:09
4

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
Paul Fleming
  • 24,238
  • 8
  • 76
  • 113
3

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.

tia
  • 9,518
  • 1
  • 30
  • 44
-1

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.

Lajos Arpad
  • 64,414
  • 37
  • 100
  • 175
  • 2
    I would like to know the cause of the downvote (I still don't see why was my comment wrong). Maybe the downvoter can tell me where he thinks I was incorrect. – Lajos Arpad Sep 14 '10 at 18:21
  • 1
    Not my downvote but there's several errors in your post that could have lead to it. That A == A does not guarantee A.Equals(A) for objects they are simply two method calls and the result of those calls are not guaranteed to be identical further (2) if A == B then B == A; If A.Equals(B) then B.Equals(A) (simetry) is not true either since A and B could have two different runtime types with each of there own overloads of Equals yielding different results and lastly A == B and B == C does not lead to A == C. Take this code string A = "foo"; string B = "foo"; object C = B; A == C is false – Rune FS Oct 12 '10 at 14:06
  • If you take any Object, A == A is true. If you watch whether an Object Equals itself, it will always be true. You are commenting about the == and Equals of two different Objects. (new Foo()) == (new Foo()) will never be true, but these are two different objects despite the fact that you generate them with the same syntax. A, B and C means Objects, not function calls. If you have a function call which generates Objects, the result of two different calls will be two different Objects. A == A is always true, an Object is the very same with itself. A.Equals(A) is always true, A Equals itself – Lajos Arpad Oct 12 '10 at 15:22
  • A == A means that A.OBJECT_ID == A.OBJECT_ID and A.Equals(A) which is always true (we are talking about Object's here, not function calls) – Lajos Arpad Oct 12 '10 at 15:23
  • About symmetry: I would really be eager to see an example where A == B is true and B == A is false. Note that we are talking about A and B as Objects and we are not talking about A == B true at a time, make changes to B and B == A false later, we are talking about A == B being true and B == A being false at the very same time. – Lajos Arpad Oct 12 '10 at 15:49
  • About tranzitivity: Your example unfortunately is trivially wrong: string A = "foo";string B = "foo"; Object C = B; This leads to A == B false, A.Equals(B) true, so A == B and B == C is false in the first place. I can see that you have the same problem as miliu, not understanding the difference between Equals and ==, at least he didn't write misleading examples as comments. – Lajos Arpad Oct 12 '10 at 15:52
  • 1
    In C#, the `==` token is used to represent two different operators: an overloadable (but non-virtual) equality operator and a fixed reference-equality operator. If the `==` operator is used with a combination of types for which an overload has been explicitly defined (e.g. comparing two strings), it will use that overload. A comparison between two arguments of type `String` will thus check strings for identical content, while a comparison between a `String` and an `Object` will check the object-ids of its arguments. – supercat Sep 27 '13 at 19:14
  • `new Foo() == new Foo()` will be true, if `Foo`, for example, is defined thus: `public class Foo { public string P { get; set; } public static bool operator ==(Foo a, Foo b) { return a.P == b.P; } public static bool operator !=(Foo a, Foo b) { return !(a == b); } }` – phoog Jan 08 '15 at 15:06
  • @phoog right, but in order to achieve that you need to override the == operator. – Lajos Arpad Jun 20 '19 at 08:33