You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
OrdinalIgnoreCase folds "ſ" (U+017F, long s) to "S", so "ſtraße2.txt" sorts before "Tag1.txt" where StringComparison.OrdinalIgnoreCase puts it after #79
NaturalStringComparer.OrdinalIgnoreCase is documented as folding text "as StringComparison.OrdinalIgnoreCase folds it" (Sorting/NaturalStringComparer.cs, the class remarks and the doc comments on CompareFolded and Fold). Both fold paths call char.ToUpperInvariant:
CompareFolded, line 259: char.ToUpperInvariant(x[xStart + offset]).CompareTo(char.ToUpperInvariant(y[yStart + offset]))
Fold, line 276: ignoreCase ? char.ToUpperInvariant(value) : value
char.ToUpperInvariant('ſ') (U+017F LATIN SMALL LETTER LONG S) returns 'S'. StringComparison.OrdinalIgnoreCase does not treat ſ as s or S, though: it uses simple case mapping, which leaves U+017F alone. On .NET 10:
(int)char.ToUpperInvariant('ſ');// 83 ('S')string.Equals("ſ","s",StringComparison.OrdinalIgnoreCase);// Falsestring.Compare("ſ","t",StringComparison.OrdinalIgnoreCase);// 299 (ſ after t)NaturalStringComparer.OrdinalIgnoreCase.Compare("ſ","t");// -1 (ſ before t)
An exhaustive check of every BMP character shows U+017F is the only one where the two folds disagree. The order is still transitive, because the same fold is used everywhere, so the only thing that breaks is the documented parity with OrdinalIgnoreCase. The letter shows up in older German and other historical texts, and in transcriptions of them.
This is separate from #77 / PR #78, which covers letters outside the BMP. PR #78 keeps char.ToUpperInvariant for BMP characters, and its Compare_IgnoringCase_FoldsAsOrdinalIgnoreCaseDoes parity test doesn't include ſ.
Suggested fix
Leave U+017F unfolded in both Fold and CompareFolded, for example value == 'ſ' ? value : char.ToUpperInvariant(value) behind one shared helper. Do the same in the code-point fold if #78 lands first. Alternatively, drop the "as OrdinalIgnoreCase folds it" claim and document the exception.
Acceptance criteria
With ignore case on, Compare("ſ", "t") is positive and Compare("ſ", "s") and Compare("ſ", "S") aren't decided by case folding alone (they agree in sign with string.Compare(..., StringComparison.OrdinalIgnoreCase)).
"ſ", "s", "S" and "t" are added to the Compare_IgnoringCase_FoldsAsOrdinalIgnoreCaseDoes parity test.
Activity
matt-edmondson commentedon Nov 5, 2026
Triage
StringComparison.OrdinalIgnoreCase)NaturalStringComparercase folding (Fold/CompareFolded)Generated by Claude Code