431

I recently was introduced to a large codebase and noticed all string comparisons are done using String.Equals() instead of ==

What's the reason for this, do you think?

2
  • 62
    Re : Duplicate closure - note that this question is specifically for string, whereas the accepted answer in the referenced duplicate refers to the general case of object. This is significantly different in .Net vs Java as == can be used to compare string contents in C#. Commented Feb 26, 2014 at 7:22
  • 4
    Equals may be preferred since the appearance of the null-propagation operator, because you may return false also in case of both string null, in a very concise way; like this: if(firstString?.Equals(secondString) ?? false) Commented Feb 10, 2017 at 9:57

8 Answers 8

426

It's entirely likely that a large portion of the developer base comes from a Java background where using == to compare strings is wrong and doesn't work.

In C# there's no (practical) difference (for strings) as long as they are typed as string.

If they are typed as object or T then see other answers here that talk about generic methods or operator overloading as there you definitely want to use the Equals method.

Sign up to request clarification or add additional context in comments.

23 Comments

Not as far as the values returned goes. .Equals does offer more options but str1.Equals(str2) is the same as str1 == str2 as far as what you get as a result. I do believe they use different methods for determining the equality as Jon Skeets quote that Blaenk brings up would indicate, but they give the same values.
@Yuriy Perhaps you could elaborate or provide a link on the differences?
There is a big difference. If one of the strings are null, then .Equals will throw an exception.
@Mas: The only exception ever thrown by .Equals is a null reference exception if you try to call it on an object that is null, ie. string str1 = null; str1.Equals(). This isn't an issue with Equals(), that happens with any function and should be self-evident. The actual functionality of the two comparisons is always exactly the same given the same inputs.
The link above doesn't work any longer, but this one does: dotnetperls.com/string-equals
|
138

There is practical difference between string.Equals and ==

bool result = false;

object obj = "String";    
string str2 = "String";
string str3 = typeof(string).Name;
string str4 = "String";
object obj2 = str3;

// Comparision between object obj and string str2 -- Com 1
result = string.Equals(obj, str2);// true
result = String.ReferenceEquals(obj, str2); // true
result = (obj == str2);// true

// Comparision between object obj and string str3 -- Com 2
result = string.Equals(obj, str3);// true
result = String.ReferenceEquals(obj, str3); // false
result = (obj == str3);// false

// Comparision between object obj and string str4 -- Com 3
result = string.Equals(obj, str4);// true
result = String.ReferenceEquals(obj, str4); // true
result = (obj == str4);// true

// Comparision between string str2 and string str3 -- Com 4
result = string.Equals(str2, str3);// true
result = String.ReferenceEquals(str2, str3); // false
result = (str2 == str3);// true

// Comparision between string str2 and string str4 -- Com 5
result = string.Equals(str2, str4);// true
result = String.ReferenceEquals(str2, str4); // true
result = (str2 == str4);// true

// Comparision between string str3 and string str4 -- Com 6
result = string.Equals(str3, str4);// true
result = String.ReferenceEquals(str3, str4); // false
result = (str3 == str4);// true

// Comparision between object obj and object obj2 -- Com 7
result = String.Equals(obj, obj2);// true
result = String.ReferenceEquals(obj, obj2); // false
result = (obj == obj2);// false

Adding Watch

obj     "String" {1#}   object {string}
str2    "String" {1#}   string
str3    "String" {5#}   string
str4    "String" {1#}   string
obj2    "String" {5#}   object {string}

Now look at {1#} and {5#}

obj, str2, str4 and obj2 references are same.

obj and obj2 are object type and others are string type

Conclusion:

  1. com1: result = (obj == str2);// true
    • compares object and string so performs a reference equality check
    • obj and str2 point to the same reference so the result is true
  2. com2: result = (obj == str3);// false
    • compares object and string so performs a reference equality check
    • obj and str3 point to the different references so the result is false
  3. com3: result = (obj == str4);// true
    • compares object and string so performs a reference equality check
    • obj and str4 point to the same reference so the result is true
  4. com4: result = (str2 == str3);// true
    • compares string and string so performs a string value check
    • str2 and str3 are both "String" so the result is true
  5. com5: result = (str2 == str4);// true
    • compares string and string so performs a string value check
    • str2 and str4 are both "String" so the result is true
  6. com6: result = (str3 == str4);// true
    • compares string and string so performs a string value check
    • str3 and str4 are both "String" so the result is true
  7. com7: result = (obj == obj2);// false  - compares object and object so performs a reference equality check      - obj and obj2 point to the different references so the result is false

7 Comments

Simple explanation for all cases above: string.Equals(object, object) is object.Equals(object, object) which calls Equals on the first argument, unlike == operator, which, unless overloaded, calls ReferenceEquals.
I believe that the reason that "String" and typeof(string).Name are different references is that the former, as a compile-time constant, is interned, whereas the latter, as a run-time value, is not.
Why do you use camelCase string for Equals but CapCase String for ReferenceEquals?
@AlexMcMillan, I could use string instead String as string is just alias of System.String
re: "ComX". Back in the bad old days of DOS, I received some data in which someone had named the file "com3" ("Com" for "community" in this case, not "comparison")..Of course when trying to access the file, the OS thought it was a com port, with interesting results. 30 years later, but I refrain from naming ''anything'' "com-whatever" unless it really is a com port.
|
105

There is one subtle but very important difference between == and the String.Equals methods:

class Program
{
    static void Main(string[] args)
    {
        CheckEquality("a", "a");
        Console.WriteLine("----------");
        CheckEquality("a", "ba".Substring(1));
    }

    static void CheckEquality<T>(T value1, T value2) where T : class
    {
        Console.WriteLine("value1: {0}", value1);
        Console.WriteLine("value2: {0}", value2);

        Console.WriteLine("value1 == value2:      {0}", value1 == value2);
        Console.WriteLine("value1.Equals(value2): {0}", value1.Equals(value2));

        if (typeof(T).IsEquivalentTo(typeof(string)))
        {
            string string1 = (string)(object)value1;
            string string2 = (string)(object)value2;
            Console.WriteLine("string1 == string2:    {0}", string1 == string2);
        }
    }
}

Produces this output:

value1: a
value2: a
value1 == value2:      True
value1.Equals(value2): True
string1 == string2:    True
----------
value1: a
value2: a
value1 == value2:      False
value1.Equals(value2): True
string1 == string2:    True

You can see that the == operator is returning false to two obviously equal strings. Why? Because the == operator in use in the generic method is resolved to be the op_equal method as defined by System.Object (the only guarantee of T the method has at compile time), which means that it's reference equality instead of value equality.

When you have two values typed as System.String explicitly, then == has a value-equality semantic because the compiler resolves the == to System.String.op_equal instead of System.Object.op_equal.

So to play it safe, I almost always use String.Equals instead to that I always get the value equality semantics I want.

And to avoid NullReferenceExceptions if one of the values is null, I always use the static String.Equals method:

bool true = String.Equals("a", "ba".Substring(1));

6 Comments

A related gotcha occurs with code that returns things of type Object. If var myItem=GetItem() reads an Object from the database, the expressions "Hi".Equals(myItem), myItem.Equals("Hi"), and Object.Equals("Hi", myItem) will all return True if the returned object is a string with two characters "Hi", but myItem == "Hi" or "Hi" == myItem will test reference equality. The "Option Strict On" dialect of VB.NET is better in that regard. Its "=" operator tests either tests value equality or won't compile; for a reference-equality check, one uses the "Is" operator.
@AndrewArnott If I execute "a" == "ab".Substring(0, 1) on csharppad.com I get back true. Same if I use String objects. Explanation?
Because in your example, the compiler knows to invoke the System.String overload of the == operator. The case you have to be cautious of is when the compiler doesn't know what type it is, such as when the compared expressions are typed as T or System.Object.
@AndrewArnott Could you elaborate on why .Substring() causes different behavior? As it returns a string, not a string wrapped in an object, why does it not have the same behavior as the first check? To me, it seems like both should have the exact same output. It's guaranteed to be a string comparison in the second example, just as it is in the first.
@Rob The comparison is done in a generic method that does not know when it is compiled that it will be doing a string comparison. Therefore the == operator used in the generic method is an object == operator rather than a string== operator. The reason .Substring(1) produces a different result is that the method creates a new string object rather than possibly reusing another one that already has the same value. Thus as the two strings with equivalent values but different objects are seen as different by the generic method.
|
55

String.Equals does offer overloads to handle casing and culture-aware comparison. If your code doesn't make use of these, the devs may just be used to Java, where (as Matthew says), you must use the .Equals method to do content comparisons.

2 Comments

This should get much higher, as culture-aware (and explicit case sensitivity) comparisons are better practive - e.g. learn.microsoft.com/en-us/dotnet/standard/base-types/…
Agree with @Bartosz! Making your intent clear is Microsoft best practice.Using == does not explicitly tell the reader what is expected to happen with regard to case-sensitivity and culture.
38

Both methods do the same functionally - they compare values.
As is written on MSDN:

But if one of your string instances is null, these methods are working differently:

string x = null;
string y = "qq";
if (x == y) // returns false
    MessageBox.Show("true");
else
    MessageBox.Show("false");

if (x.Equals(y)) // returns System.NullReferenceException: Object reference not set to an instance of an object. - because x is null !!!
    MessageBox.Show("true");
else
    MessageBox.Show("false");

Comments

17

There's a writeup on this article which you might find to be interesting, with some quotes from Jon Skeet. It seems like the use is pretty much the same.

Jon Skeet states that the performance of instance Equals "is slightly better when the strings are short—as the strings increase in length, that difference becomes completely insignificant."

2 Comments

What exactly qualifies as a "short" string?
@AndHeCodedIt Depends on the speed of your system I presume. Run a benchmark.
10

I want to add that there is another difference. It is related to what Andrew posts.

It is also related to a VERY annoying to find bug in our software. See the following simplified example (I also omitted the null check).

public const int SPECIAL_NUMBER = 213;

public bool IsSpecialNumberEntered(string numberTextBoxTextValue)
{
    return numberTextBoxTextValue.Equals(SPECIAL_NUMBER)
}

This will compile and always return false. While the following will give a compile error:

public const int SPECIAL_NUMBER = 213;

public bool IsSpecialNumberEntered(string numberTextBoxTextValue)
{
    return (numberTextBoxTextValue == SPECIAL_NUMBER);
}

We have had to solve a similar problem where someone compared enums of different type using Equals. You are going to read over this MANY times before realising it is the cause of the bug. Especially if the definition of SPECIAL_NUMBER is not near the problem area.

This is why I am really against the use of Equals in situations where is it not necessary. You lose a little bit of type-safety.

2 Comments

that is why it is good to use the static string.Equals method always with the StringComparison argument - that overload will produce a compile error when trying to compare strings and integers
I wish method and operator parameters could have attributes (and that Equals and ReferenceEquals had always used them) that would disallow implicit conversions, upcasts, boxing conversions, or combinations of the above. I also wish some operator other than == had been used for reference equality. Having Equals and == define equivalence relations with all argument types where they compile would be more helpful than having them compile in cases that do not yield equivalence relations.
6

I've just been banging my head against a wall trying to solve a bug because I read this page and concluded there was no meaningful difference when in practice there is so I'll post this link here in case anyone else finds they get different results out of == and equals.

Object == equality fails, but .Equals succeeds. Does this make sense?

string a = "x";
string b = new String(new []{'x'});

Console.WriteLine("x == x " + (a == b));//True
Console.WriteLine("object x == x " + ((object)a == (object)b));//False
Console.WriteLine("x equals x " + (a.Equals(b)));//True
Console.WriteLine("object x equals x " + (((object)a).Equals((object)b)));//True

1 Comment

As I understand it, the second equality check fails as Object == Object resolves to System.Object.ReferenceEquals and a and b are two different objects in memory. The remainder compare the values of the string objects. Even the last comparison, which seems to call .Equals for the Object type object, as it is implemented as a virtual method, it defaults to calling String.Equals.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.