When comparing two strings in c# for equality, what is the difference between InvariantCulture and Ordinal comparison?
9 Answers
InvariantCulture
Uses a "standard" set of character orderings (a,b,c, ... etc.). This is in contrast to some specific locales, which may sort characters in different orders ('a-with-acute' may be before or after 'a', depending on the locale, and so on).
Ordinal
On the other hand, looks purely at the values of the raw byte(s) that represent the character.
There's a great sample at http://msdn.microsoft.com/en-us/library/e6883c06.aspx that shows the results of the various StringComparison values. All the way at the end, it shows (excerpted):
StringComparison.InvariantCulture:
LATIN SMALL LETTER I (U+0069) is less than LATIN SMALL LETTER DOTLESS I (U+0131)
LATIN SMALL LETTER I (U+0069) is less than LATIN CAPITAL LETTER I (U+0049)
LATIN SMALL LETTER DOTLESS I (U+0131) is greater than LATIN CAPITAL LETTER I (U+0049)
StringComparison.Ordinal:
LATIN SMALL LETTER I (U+0069) is less than LATIN SMALL LETTER DOTLESS I (U+0131)
LATIN SMALL LETTER I (U+0069) is greater than LATIN CAPITAL LETTER I (U+0049)
LATIN SMALL LETTER DOTLESS I (U+0131) is greater than LATIN CAPITAL LETTER I (U+0049)
You can see that where InvariantCulture yields (U+0069, U+0049, U+00131), Ordinal yields (U+0049, U+0069, U+00131).
Pointing to Best Practices for Using Strings in the .NET Framework:
- Use
StringComparison.Ordinal
orStringComparison.OrdinalIgnoreCase
for comparisons as your safe default for culture-agnostic string matching. - Use comparisons with
StringComparison.Ordinal
orStringComparison.OrdinalIgnoreCase
for better performance. - Use the non-linguistic
StringComparison.Ordinal
orStringComparison.OrdinalIgnoreCase
values instead of string operations based onCultureInfo.InvariantCulture
when the comparison is linguistically irrelevant (symbolic, for example).
And finally:
- Do not use string operations based on
StringComparison.InvariantCulture
in most cases. One of the few exceptions is when you are persisting linguistically meaningful but culturally agnostic data.
Another handy difference (in English where accents are uncommon) is that an InvariantCulture comparison compares the entire strings by case-insensitive first, and then if necessary (and requested) distinguishes by case after first comparing only on the distinct letters. (You can also do a case-insensitive comparison, of course, which won't distinguish by case.) Corrected: Accented letters are considered to be another flavor of the same letters and the string is compared first ignoring accents and then accounting for them if the general letters all match (much as with differing case except not ultimately ignored in a case-insensitive compare). This groups accented versions of the otherwise same word near each other instead of completely separate at the first accent difference. This is the sort order you would typically find in a dictionary, with capitalized words appearing right next to their lowercase equivalents, and accented letters being near the corresponding unaccented letter.
An ordinal comparison compares strictly on the numeric character values, stopping at the first difference. This sorts capitalized letters completely separate from the lowercase letters (and accented letters presumably separate from those), so capitalized words would sort nowhere near their lowercase equivalents.
InvariantCulture also considers capitals to be greater than lower case, whereas Ordinal considers capitals to be less than lowercase (a holdover of ASCII from the old days before computers had lowercase letters, the uppercase letters were allocated first and thus had lower values than the lowercase letters added later).
For example, by Ordinal: "0" < "9" < "A" < "Ab" < "Z" < "a" < "aB" < "ab" < "z" < "Á" < "Áb" < "á" < "áb"
And by InvariantCulture: "0" < "9" < "a" < "A" < "á" < "Á" < "ab" < "aB" < "Ab" < "áb" < "Áb" < "z" < "Z"
Although the question is about equality, for quick visual reference, here the order of some strings sorted using a couple of cultures illustrating some of the idiosyncrasies out there.
Ordinal 0 9 A Ab a aB aa ab ss Ä Äb ß ä äb ぁ あ ァ ア 亜 A
IgnoreCase 0 9 a A aa ab Ab aB ss ä Ä äb Äb ß ぁ あ ァ ア 亜 A
--------------------------------------------------------------------
InvariantCulture 0 9 a A A ä Ä aa ab aB Ab äb Äb ss ß ァ ぁ ア あ 亜
IgnoreCase 0 9 A a A Ä ä aa Ab aB ab Äb äb ß ss ァ ぁ ア あ 亜
--------------------------------------------------------------------
da-DK 0 9 a A A ab aB Ab ss ß ä Ä äb Äb aa ァ ぁ ア あ 亜
IgnoreCase 0 9 A a A Ab aB ab ß ss Ä ä Äb äb aa ァ ぁ ア あ 亜
--------------------------------------------------------------------
de-DE 0 9 a A A ä Ä aa ab aB Ab äb Äb ß ss ァ ぁ ア あ 亜
IgnoreCase 0 9 A a A Ä ä aa Ab aB ab Äb äb ss ß ァ ぁ ア あ 亜
--------------------------------------------------------------------
en-US 0 9 a A A ä Ä aa ab aB Ab äb Äb ß ss ァ ぁ ア あ 亜
IgnoreCase 0 9 A a A Ä ä aa Ab aB ab Äb äb ss ß ァ ぁ ア あ 亜
--------------------------------------------------------------------
ja-JP 0 9 a A A ä Ä aa ab aB Ab äb Äb ß ss ァ ぁ ア あ 亜
IgnoreCase 0 9 A a A Ä ä aa Ab aB ab Äb äb ss ß ァ ぁ ア あ 亜
Observations:
de-DE
,ja-JP
, anden-US
sort the same wayInvariant
only sortsss
andß
differently from the above three culturesda-DK
sorts quite differently- the
IgnoreCase
flag matters for all sampled cultures
The code used to generate above table:
var l = new List<string>
{ "0", "9", "A", "Ab", "a", "aB", "aa", "ab", "ss", "ß",
"Ä", "Äb", "ä", "äb", "あ", "ぁ", "ア", "ァ", "A", "亜" };
foreach (var comparer in new[]
{
StringComparer.Ordinal,
StringComparer.OrdinalIgnoreCase,
StringComparer.InvariantCulture,
StringComparer.InvariantCultureIgnoreCase,
StringComparer.Create(new CultureInfo("da-DK"), false),
StringComparer.Create(new CultureInfo("da-DK"), true),
StringComparer.Create(new CultureInfo("de-DE"), false),
StringComparer.Create(new CultureInfo("de-DE"), true),
StringComparer.Create(new CultureInfo("en-US"), false),
StringComparer.Create(new CultureInfo("en-US"), true),
StringComparer.Create(new CultureInfo("ja-JP"), false),
StringComparer.Create(new CultureInfo("ja-JP"), true),
})
{
l.Sort(comparer);
Console.WriteLine(string.Join(" ", l));
}
Invariant is a linguistically appropriate type of comparison.
Ordinal is a binary type of comparison. (faster)
See http://www.siao2.com/2004/12/29/344136.aspx
Here is an example where string equality comparison using InvariantCultureIgnoreCase and OrdinalIgnoreCase will not give the same results:
string str = "\xC4"; //A with umlaut, Ä
string A = str.Normalize(NormalizationForm.FormC);
//Length is 1, this will contain the single A with umlaut character (Ä)
string B = str.Normalize(NormalizationForm.FormD);
//Length is 2, this will contain an uppercase A followed by an umlaut combining character
bool equals1 = A.Equals(B, StringComparison.OrdinalIgnoreCase);
bool equals2 = A.Equals(B, StringComparison.InvariantCultureIgnoreCase);
If you run this, equals1 will be false, and equals2 will be true.
No need to use fancy unicode char exmaples to show the difference. Here's one simple example I found out today which is surprising, consisting of only ASCII characters.
According to the ASCII table, 0
(0x48) is smaller than _
(0x95) when compared ordinally. InvariantCulture would say the opposite (PowerShell code below):
PS> [System.StringComparer]::Ordinal.Compare("_", "0")
47
PS> [System.StringComparer]::InvariantCulture.Compare("_", "0")
-1
String1.Equals(String2, StringComparison.Ordinal)
, you better useString1 == String2
which is intrinsicallyString1.Equals(String2)
and it is by default an ordinal case-sensitive comparison. – Ghasan غسان==
"better", but it is a) shorter, b) less explicit about what exactly it does and c)String1
can be null without the comparison throwing aNullReferenceException
. – Evgeniy BerezovskyStringComparison
type. In the case of string comparison, it meansString.Equals
. – Ohad SchneiderNullReferenceException
you can simply use the static method:String.Equals(string1, string2, StringComparison.Ordinal)
. – Ohad Schneider