C# String Equals vs ==: What’s the Difference?
c# string equals vs ==: Understand the technical difference between == and Equals for strings in C#, including reference equality, interning, and culture-sensitive com...
When comparing strings in C#, the choice between == and Equals can lead to subtle differences in behavior. The question c# string equals vs == is common because both appear to do the same thing, but they operate at different levels of abstraction. == is an operator that can be overloaded, while Equals is a virtual method defined on object and overridden by string. For most everyday string comparisons, they return the same result, but there are important edge cases where they diverge.
The Core Difference Between == and Equals for Strings
The == operator in C# is not a direct language construct for value equality; it is a static operator that can be overloaded per type. The string type overloads == to perform an ordinal (byte-by-byte) comparison of the characters. On the other hand, Equals is an instance method that is overridden in string to also perform an ordinal comparison by default. However, Equals has additional overloads that accept a StringComparison enumeration, allowing culture-aware, case-insensitive, or ordinal-ignore-case comparisons. That flexibility is the primary reason you might choose one over the other.
string first = "hello"; string second = "hello"; Console.WriteLine(first == second); // True Console.WriteLine(first.Equals(second)); // True
In this basic example, both produce the same result. The difference becomes visible when you consider reference equality, null handling, and culture-specific rules.
How String Equality Works Under the Hood
In .NET, strings are reference types, but they behave like value types for equality because the string class overrides both the == operator and the Equals method to compare the actual character sequence. The default object equality would compare references, but string replaces that behavior. This means that two distinct string instances with the same content are considered equal by both == and Equals.
string a = new string(new char[] { 'h', 'i' }); string b = new string(new char[] { 'h', 'i' }); Console.WriteLine(a == b); // True Console.WriteLine(a.Equals(b)); // True
Both allocate new objects, but the comparison is based on content, not identity. This is a deliberate design decision to make strings easier to work with.
Reference Equality vs Value Equality
The == operator, when not overloaded, performs reference equality for reference types. For strings, the overload changes that to value equality. However, you can still force reference equality by casting to object or using ReferenceEquals.
string s1 = "hello"; string s2 = "hello"; string s3 = new string(new char[] { 'h', 'e', 'l', 'l', 'o' }); Console.WriteLine((object)s1 == (object)s2); // True due to interning Console.WriteLine((object)s1 == (object)s3); // False Console.WriteLine(ReferenceEquals(s1, s3)); // False
String interning is the key here. The runtime maintains a table of interned strings, and string literals are automatically interned. So s1 and s2 point to the same interned object. s3 is created at runtime and is not interned, so it has a different reference. This is a common source of confusion when developers mix == with casts or use ReferenceEquals.
When == and Equals Give Different Results
The most significant divergence occurs when you use the Equals overload with a StringComparison value. The == operator does not have a culture-aware variant; it always performs an ordinal comparison. If you need case-insensitive or culture-specific comparison, you must use Equals with an explicit StringComparison.
string word = "Straße"; string other = "STRASSE"; Console.WriteLine(word == other); // False Console.WriteLine(word.Equals(other, StringComparison.CurrentCulture)); // True in German culture
Another difference is that == is statically resolved based on the compile-time type. If you have a variable typed as object that holds a string, == will use object's reference equality, not the string overload, because operator overloading is not polymorphic.
object obj = "hello"; string str = "hello"; Console.WriteLine(obj == str); // False (reference comparison) Console.WriteLine(obj.Equals(str)); // True (virtual dispatch)
This is a classic pitfall. The Equals method is virtual, so the runtime calls string.Equals even when the static type is object. The == operator is not virtual, so it uses the operator defined on the compile-time type.
Null Handling and Empty Strings
Null handling is another area where == and Equals behave differently. Calling an instance method on a null reference throws a NullReferenceException. The == operator, however, can safely compare a null reference with another string.
string nullString = null; string emptyString = ""; Console.WriteLine(nullString == emptyString); // False Console.WriteLine(nullString == null); // True // The following line throws NullReferenceException // Console.WriteLine(nullString.Equals(emptyString));
If you are not certain whether a string is null, use == or the static string.Equals method, which handles nulls gracefully:
Console.WriteLine(string.Equals(nullString, emptyString)); // False
The static string.Equals method is a good choice when you need a null-safe comparison without worrying about the instance being null.
Performance and Allocation Considerations
Performance is a common concern when choosing between == and Equals. For strings, both == and the parameterless Equals perform an ordinal comparison that checks length first and then each character. The runtime cost is nearly identical. The == operator is a static method call, while Equals is a virtual call, but the JIT can often inline or devirtualize these calls in practice.
When you use Equals with a StringComparison such as CurrentCulture or InvariantCulture, the comparison becomes significantly more expensive because it must apply culture-specific rules, including case folding and sorting. If you only need case-insensitive comparison, use StringComparison.OrdinalIgnoreCase instead of a culture-aware option. Ordinal comparisons are faster and more predictable.
// Faster, culture-insensitive bool equal = a.Equals(b, StringComparison.OrdinalIgnoreCase); // Slower, culture-aware bool equalCulture = a.Equals(b, StringComparison.CurrentCultureIgnoreCase);
Do not assume that == is always faster than Equals; the difference is negligible for typical string lengths. The real performance factor is the the comparison mode you choose.
Choosing the Right Comparison for Your Code
The decision between == and Equals should be based on the semantics you need, not on habit. Use == when you want a simple ordinal comparison and you are certain both operands are strings or can be null safely. Use the instance Equals when you need to specify a StringComparison or when you are working with a variable typed as object and want polymorphic behavior. Use the static string.Equals when you need a null-safe comparison with an explicit comparison mode.
For most application code, == is the clearest and most idiomatic choice for string equality. It reads naturally and avoids the risk of a NullReferenceException. However, when you need case-insensitive or culture-aware behavior, you must switch to Equals with an explicit StringComparison. Always avoid relying on reference equality for strings unless you are deliberately checking for interning or identity, and even then, prefer ReferenceEquals to make your intent explicit.
Understanding the difference between == and Equals for strings prevents subtle bugs that are difficult to reproduce. The key is to remember that == is not polymorphic, Equals is, and that string interning can make reference comparisons appear to work when they are not guaranteed to. By choosing the comparison method that matches your exact requirement, you keep your code predictable and maintainable.