Back to Blog
C#

C# String Comparison: Ordinal vs Culture

c# string comparison: Learn how to compare strings in C# correctly: ordinal vs culture-sensitive, == vs Equals, and when to use StringComparison.

StringComparisonOrdinal comparisonCulture-sensitive comparisonString.Equals
Diagram comparing C# string comparison methods with ordinal and culture-sensitive paths.

c# string comparison requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.

When you write if (a == b) in C#, you might expect a simple value comparison. For strings, that operator performs an ordinal comparison, which is case-sensitive and culture-insensitive. That behavior is often correct, but not always. This article covers the full set of string comparison options in C#, including string.Equals, String.Compare, and the StringComparison enum, so you can choose the right method for your scenario.

The == Operator and Ordinal Comparison

The == operator on strings is overloaded to perform an ordinal comparison by default. Ordinal comparison examines the Unicode code points of each character, treating the string as a sequence of binary values. It is case-sensitive and ignores any linguistic rules such as accent folding or cultural sorting order.

string a = "Hello"; string b = "hello"; Console.WriteLine(a == b); // False

This is usually what you want for internal identifiers, file paths, or any string that must match exactly. However, == does not give you the option to change the comparison rules. If you need case-insensitive or culture-aware behavior, you must use a different method.

Using string.Equals for More Control

The string.Equals method provides overloads that accept a StringComparison enum, giving you explicit control over how the comparison is performed. The static version string.Equals(a, b, StringComparison) is the most common.

string a = "Hello"; string b = "hello"; bool areEqual = string.Equals(a, b, StringComparison.OrdinalIgnoreCase); Console.WriteLine(areEqual); // True

Unlike ==, string.Equals can be called on a null instance without throwing a NullReferenceException when used as a static method. This makes it safer when either operand might be null.

The StringComparison Enum

The StringComparison enum defines six values that determine how strings are compared. Each value combines a culture rule with case sensitivity.

ValueBehaviorUse case
CurrentCultureCulture-sensitive, case-sensitiveUser-facing text that must respect the user's language
CurrentCultureIgnoreCaseCulture-sensitive, case-insensitiveUser-facing text with case-insensitive matching
InvariantCultureCulture-insensitive, case-sensitiveCross-culture data that must be consistent
InvariantCultureIgnoreCaseCulture-insensitive, case-insensitiveCross-culture data with case-insensitive matching
OrdinalBinary comparison, case-sensitiveInternal identifiers, file paths, exact matching
OrdinalIgnoreCaseBinary comparison, case-insensitiveInternal identifiers where case should be ignored

The choice between CurrentCulture and InvariantCulture matters when the strings contain characters that sort differently across cultures. For example, the German letter "ß" compares differently in German and English cultures. InvariantCulture uses a fixed set of rules based on English, so it produces the same result on every machine.

Comparing for Sorting: String.Compare and CompareTo

When you need to order strings, you use String.Compare or the instance method CompareTo. These return an integer that indicates the relative order: negative if the first string precedes the second, zero if they are equal, and positive if the first follows the second.

int result = string.Compare("apple", "banana", StringComparison.Ordinal); Console.WriteLine(result); // Negative value

The CompareTo method is equivalent but does not accept a StringComparison parameter; it always uses the current culture. For this reason, String.Compare is preferred when you need to specify the comparison rule.

string x = "apple"; string y = "banana"; int order = x.CompareTo(y); // Culture-sensitive, case-sensitive

Be aware that the exact integer value is not guaranteed to be -1, 0, or 1. You should only check the sign, not the magnitude.

Performance and Culture Considerations

Ordinal comparison is significantly faster than culture-sensitive comparison because it does not need to apply linguistic rules. Culture-sensitive comparisons involve loading culture data and performing complex string normalization, which can be orders of magnitude slower in tight loops.

If you are comparing strings in a performance-critical path, such as a hash table lookup or a sorting algorithm, use Ordinal or OrdinalIgnoreCase unless you have a concrete reason to use culture. Ordinal comparison is also deterministic: it produces the same result regardless of the machine's culture settings. This is crucial for server-side code that may run in different environments.

Culture-sensitive comparison can also lead to subtle bugs. For example, two strings that are considered equal under one culture might be different under another. If you store data with a culture-sensitive comparison and later read it on a machine with a different culture, the comparison results may change. Using InvariantCulture or Ordinal avoids this by providing a stable rule.

Common Pitfalls and How to Avoid Them

One frequent mistake is using == on variables typed as object rather than string. When the compile-time type is object, the == operator performs reference equality, not value equality.

object a = "Hello"; object b = "Hello"; Console.WriteLine(a == b); // False (reference comparison) Console.WriteLine(a.Equals(b)); // True (value comparison)

Another pitfall is assuming that case-insensitive comparison is always culture-insensitive. StringComparison.CurrentCultureIgnoreCase can produce unexpected results when the culture changes. For internal tokens like API keys or database identifiers, use OrdinalIgnoreCase instead.

Finally, do not use String.Compare to check for equality. If you only need to know whether two strings are equal, string.Equals is clearer and more efficient. String.Compare is meant for ordering, and its result is not guaranteed to be exactly zero for equal strings in all implementations.

Choosing the Right Comparison for Your Scenario

The decision comes down to what the strings represent and where the comparison happens.

Use Ordinal or OrdinalIgnoreCase for:

  • File paths, URLs, and internal identifiers
  • Configuration keys and environment variable names
  • Any string that must be compared exactly and consistently across machines

Use CurrentCulture or CurrentCultureIgnoreCase for:

  • User-facing text like names, addresses, or product names that must follow the user's language rules
  • Sorting displayed lists according to the user's cultural expectations

Use InvariantCulture or InvariantCultureIgnoreCase for:

  • Data that is stored or transmitted between systems and must be compared consistently regardless of the local culture
  • Protocol strings or serialized data where culture-dependent behavior would cause incompatibility

When you are unsure, start with Ordinal. It is the safest default for most internal logic. Only switch to a culture-sensitive comparison when you have a clear requirement to respect linguistic rules. This keeps your code predictable and avoids the performance and portability issues that come with culture-sensitive string comparison in C#.

c# string comparison: Practical Usage and Code Examples | RYUSLOG DEV