Difference between InvariantCulture and Ordinal string comparison

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


It does matter, for example - there is a thing called character expansion

var s1 = "Strasse";
var s2 = "Straße";

s1.Equals(s2, StringComparison.Ordinal);           //false
s1.Equals(s2, StringComparison.InvariantCulture);  //true

With InvariantCulture the ß character gets expanded to ss.


Pointing to Best Practices for Using Strings in the .NET Framework:

  • Use StringComparison.Ordinal or StringComparison.OrdinalIgnoreCase for comparisons as your safe default for culture-agnostic string matching.
  • Use comparisons with StringComparison.Ordinal or StringComparison.OrdinalIgnoreCase for better performance.
  • Use the non-linguistic StringComparison.Ordinal or StringComparison.OrdinalIgnoreCase values instead of string operations based on CultureInfo.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, and en-US sort the same way
  • Invariant only sorts ss and ß differently from the above three cultures
  • da-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));
}

Comments

  1. Kelly

    • 2016/7/13

    (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)

  2. Roland

    • 2019/7/25

    Just to add another similar example but with string literals, if a="\x00e9" (e acute) and b="\x0065\x0301" (e combined with an acute accent), StringComparer.Ordinal.Equals(a, b) will return false while StringComparer.InvariantCulture.Equals(a, b) will return true.

  3. Cattaneo

    • 2015/1/14

    One of the clearest differences between ordinal and culture-sensitive comparison (including comparisons that use the invariant culture) concerns 

  4. Jensen

    • 2020/8/11

    What's the difference between the InvariantCulture and original comparison? #1 building. For example, it's really important - there's something called role extension . var s1 = "Strasse"; var s2 = "Straße"; s1.Equals(s2, StringComparison.Ordinal); //false s1.Equals(s2, StringComparison.InvariantCulture); //true With InvariantCulture, the role is extended to ss. #2 building

  5. Martin

    • 2018/3/28

    When the comparison is language independent (for example, symbols), use a non language StringComparison.Ordinal or StringComparison.

  6. Theodore

    • 2020/2/7

    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+

  7. Colin

    • 2018/2/11

    Invariant Culture – Use a default setting which will be identical on every computer. String Equality and string Ordering. When we compare two 

  8. Jamari

    • 2018/9/7

    Use the non-linguistic StringComparison.Ordinal or StringComparison.OrdinalIgnoreCase values instead of string operations based on CultureInfo.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.

  9. Eduardo

    • 2016/8/3

    In .NET there are 6 ways to compare strings: Ordinal: Performs a simple byte comparison that is independent of language. This is most 

  10. Edison

    • 2017/5/23

    Ordinal simply does a direct comparison of the Unicode character numbers. (Note: Unicode is a superset of ASCII.) InvariantCulture, on the other hand, knows about special rules. It represents the rules for a "neutral" culture and for the most part does things in a way that would be considered normal in the United States.

  11. Ishaan

    • 2021/6/6

    Ordinal or StringComparison.OrdinalIgnoreCase values instead of string operations based on CultureInfo.InvariantCulture when the comparison is linguistically 

  12. Mitchell

    • 2017/1/7

    Difference between InvariantCulture and Ordinal string comparison. Solution: InvariantCulture. Uses a "standard" set of character orderings (a,b,c, etc 

  13. Adriel

    • 2017/9/28

    Ordinal or StringComparison.InvariantCulture? There is a good article explaining the differences between the StringComparison enum 

  14. Luka

    • 2015/11/15

    Use the StringComparison enum type, and the StringComparer Ordinal enum. Meanwhile, StringComparer is a class we can use to compare 2 strings in a 

Comments are closed.

Recent Posts