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?

6
  • I think string.Equals matches the whole object Commented Sep 9, 2010 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). Commented Sep 9, 2010 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. Commented Sep 9, 2010 at 18:07
  • 8
    @Robaticus, I don't understand what you are talking about. Commented Sep 9, 2010 at 18:17
  • 1
    == only works if the static type of the objects is string. Since operator overloading only takes the static type into consideration. Commented Oct 14, 2010 at 21:32

7 Answers 7

370

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 
Sign up to request clarification or add additional context in comments.

10 Comments

Doesn't == also include a Object.ReferenceEquals check for a quick true if they are the same object?
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.
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?
@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: 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 :)
|
63
+50

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.

Comments

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) 

9 Comments

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: 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.
It doesn't have to choose between them, it can do something different instead. Not often a good idea, but it can.
@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
|
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 

4 Comments

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 ==?
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#.
@miliu: As you can see from my sample this is not related to WPF but generally the case in .NET.
I believe it's actually spelled "H-e-l-l-o"
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.

5 Comments

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: 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: 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");
@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.
@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.
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 

Comments

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.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.