How to compare strings in C# — ==, Equals, StringComparison, OrdinalIgnoreCase
String comparison in C# has three layers: the == operator, the .Equals() method, and string.Compare(). Each behaves differently depending on culture and case sensitivity. Choosing the wrong one is one of the most common sources of subtle bugs.
== Operator (Ordinal, Case-Sensitive)
For string, == performs ordinal, case-sensitive comparison — byte by byte.
string a = "Hello";
string b = "Hello";
string c = "hello";
Console.WriteLine(a == b); // True
Console.WriteLine(a == c); // False — case matters
// Safe with null — no NullReferenceException
string? d = null;
Console.WriteLine(d == "Hello"); // False
Console.WriteLine(d == null); // True.Equals() — Instance Method
.Equals() with no arguments also does ordinal, case-sensitive comparison — same as ==. The difference matters when you need to pass a StringComparison flag.
string a = "Hello";
string b = "hello";
Console.WriteLine(a.Equals(b)); // False
Console.WriteLine(a.Equals(b, StringComparison.OrdinalIgnoreCase)); // True
Console.WriteLine(a.Equals(b, StringComparison.CurrentCultureIgnoreCase)); // TruePrefer
a.Equals(b, comparison)overa.ToLower() == b.ToLower()— allocating a new string just to compare is wasteful.
StringComparison Enum
| Value | Meaning |
|---|---|
Ordinal | Byte-by-byte, culture-invariant, case-sensitive. Use for identifiers, file paths, protocol values. |
OrdinalIgnoreCase | Same but ignores ASCII case. Best default for case-insensitive comparisons. |
CurrentCulture | Uses the user's OS locale rules. Use when displaying to end-users. |
CurrentCultureIgnoreCase | Culture-aware and case-insensitive. |
InvariantCulture | Culture-neutral, based on English rules. Use for data that crosses locales. |
InvariantCultureIgnoreCase | Same but case-insensitive. |
Case-Insensitive Comparison
string input = "Admin";
// Recommended — no allocation
bool isAdmin = input.Equals("admin", StringComparison.OrdinalIgnoreCase);
Console.WriteLine(isAdmin); // True
// Also works with string.Equals (static — null-safe)
bool same = string.Equals(input, "ADMIN", StringComparison.OrdinalIgnoreCase);
Console.WriteLine(same); // True
// Avoid: allocates two new strings
bool bad = input.ToLower() == "admin"; // works but wastefulstring.Compare
string.Compare() returns a negative number (less than), zero (equal), or a positive number (greater than). Use it for sorting or when you need ordering, not just equality.
string x = "apple";
string y = "Banana";
int result = string.Compare(x, y, StringComparison.OrdinalIgnoreCase);
if (result < 0)
Console.WriteLine($"{x} comes before {y}");
else if (result > 0)
Console.WriteLine($"{x} comes after {y}");
else
Console.WriteLine("Equal");
// Output: apple comes before BananaString.Equals — Static Null-Safe Version
The static string.Equals(a, b) overload handles null on either side without throwing.
string? a = null;
string? b = "hello";
// Instance call would throw NullReferenceException:
// a.Equals(b); // 💥
// Static call is null-safe:
bool eq = string.Equals(a, b, StringComparison.OrdinalIgnoreCase);
Console.WriteLine(eq); // False
bool bothNull = string.Equals(null, null);
Console.WriteLine(bothNull); // True== vs .Equals() — The Key Difference for Reference Types
For string, == is overloaded to compare by value, so == and .Equals() behave the same. This is not true for arbitrary objects.
// For string, == and .Equals() are the same:
string s1 = new string("hello");
string s2 = new string("hello");
Console.WriteLine(s1 == s2); // True (value comparison)
Console.WriteLine(s1.Equals(s2)); // True
// But for object references, == compares identity:
object o1 = new object();
object o2 = new object();
Console.WriteLine(o1 == o2); // False (different references)
Console.WriteLine(o1.Equals(o2)); // FalseChecking for Null or Empty
string? s = null;
// Best: single check for null or empty
if (string.IsNullOrEmpty(s))
Console.WriteLine("Null or empty");
// Also includes whitespace-only strings
if (string.IsNullOrWhiteSpace(s))
Console.WriteLine("Null, empty, or whitespace");Sorting Strings
When sorting user-visible strings, pass a StringComparer — it implements IComparer<string> for use with OrderBy, List.Sort, dictionaries, and sets.
var fruits = new List<string> { "Banana", "apple", "Cherry", "avocado" };
// Case-insensitive alphabetical sort
var sorted = fruits.OrderBy(f => f, StringComparer.OrdinalIgnoreCase).ToList();
Console.WriteLine(string.Join(", ", sorted));
// Output: apple, avocado, Banana, CherryDictionary with Case-Insensitive Keys
// OrdinalIgnoreCase makes lookup case-insensitive
var headers = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
["Content-Type"] = "application/json",
["Authorization"] = "Bearer token123"
};
Console.WriteLine(headers["content-type"]); // application/json
Console.WriteLine(headers["AUTHORIZATION"]); // Bearer token123Quick Decision Guide
| Scenario | Recommended approach |
|---|---|
| Compare identifiers, file paths, URLs | == or Equals(b, StringComparison.Ordinal) |
| Case-insensitive match | Equals(b, StringComparison.OrdinalIgnoreCase) |
| User-facing display sort | string.Compare(a, b, StringComparison.CurrentCulture) |
| Cross-locale data storage | StringComparison.InvariantCulture |
| Null-safe equality check | string.Equals(a, b, comparison) (static) |
| Dictionary with case-insensitive keys | new Dictionary<,>(StringComparer.OrdinalIgnoreCase) |