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.

C# Example Code
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.

C# Example Code
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)); // True

Prefer a.Equals(b, comparison) over a.ToLower() == b.ToLower() — allocating a new string just to compare is wasteful.

StringComparison Enum

ValueMeaning
OrdinalByte-by-byte, culture-invariant, case-sensitive. Use for identifiers, file paths, protocol values.
OrdinalIgnoreCaseSame but ignores ASCII case. Best default for case-insensitive comparisons.
CurrentCultureUses the user's OS locale rules. Use when displaying to end-users.
CurrentCultureIgnoreCaseCulture-aware and case-insensitive.
InvariantCultureCulture-neutral, based on English rules. Use for data that crosses locales.
InvariantCultureIgnoreCaseSame but case-insensitive.

Case-Insensitive Comparison

C# Example Code
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 wasteful

string.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.

C# Example Code
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 Banana

String.Equals — Static Null-Safe Version

The static string.Equals(a, b) overload handles null on either side without throwing.

C# Example Code
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.

C# Example Code
// 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));  // False

Checking for Null or Empty

C# Example Code
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.

C# Example Code
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, Cherry

Dictionary with Case-Insensitive Keys

C# Example Code
// 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 token123

Quick Decision Guide

ScenarioRecommended approach
Compare identifiers, file paths, URLs== or Equals(b, StringComparison.Ordinal)
Case-insensitive matchEquals(b, StringComparison.OrdinalIgnoreCase)
User-facing display sortstring.Compare(a, b, StringComparison.CurrentCulture)
Cross-locale data storageStringComparison.InvariantCulture
Null-safe equality checkstring.Equals(a, b, comparison) (static)
Dictionary with case-insensitive keysnew Dictionary<,>(StringComparer.OrdinalIgnoreCase)