Неожиданное поведение при сортировке строк с буквами и тире

Если у меня есть список строк, содержащих все числа и тире, они сортируют по возрастанию так:

s = s.OrderBy(t => t).ToList();

66-0616280-000
66-0616280-100
66-06162801000
66-06162801040

Это как и ожидалось.

Однако, если строки содержат буквы, сортировка несколько неожиданна. Например, вот тот же список строк с завершающим A, заменяющим 0s, и да, он сортируется:

66-0616280-00A
66-0616280100A
66-0616280104A
66-0616280-10A

Я бы ожидал, что они будут выглядеть так:

66-0616280-00A
66-0616280-10A
66-0616280100A
66-0616280104A

Почему сортировка ведет себя по-разному в строке, когда она содержит буквы, и когда она содержит только числа?

Спасибо заранее.

Ответ 1

Это потому, что значение по умолчанию StringComparer чувствительно к культуре. Насколько я могу судить, Comparer<string>.Default делегирует string.CompareTo(string), который использует текущую культуру:

Этот метод выполняет сравнение слов (чувствительность к регистру и чувствительность к культуре) с использованием текущей культуры. Для получения дополнительной информации о словах, строках и порядковых сортировках см. System.Globalization.CompareOptions.

Затем страница CompareOptions включает в себя:

В .NET Framework используются три разных способа сортировки: сортировка слов, сортировка строк и сортировка по порядку. Сортировка слов выполняет культурное сравнение строк. Определенные неасфальтированные символы могут иметь специальные веса, назначенные им. Например, дефис ( "-" ) может иметь очень небольшой вес, назначенный ему, чтобы "курятник" и "кооператив" отображались рядом друг с другом в отсортированном списке. Строковая сортировка похожа на сортировку слов, за исключением того, что особых случаев нет. Поэтому все неальфанумерные символы поступают перед всеми буквенно-цифровыми символами. Ordinal sort сравнивает строки, основанные на значениях Unicode для каждого элемента строки.

( "Малый вес" не совсем такой же, как "игнорируется", как указано в ответе Андрея, но эффекты здесь похожи.)

Если вы укажете StringComparer.Ordinal, вы получите результаты:

66-0616280-00A
66-0616280-10A
66-0616280100A
66-0616280104A

Укажите его как второй аргумент OrderBy:

s = s.OrderBy(t => t, StringComparer.Ordinal).ToList();

Здесь вы можете увидеть разницу:

Console.WriteLine(Comparer<string>.Default.Compare
    ("66-0616280104A", "66-0616280-10A"));
Console.WriteLine(StringComparer.Ordinal.Compare
    ("66-0616280104A", "66-0616280-10A"));

Ответ 2

Вот замечание от MSDN:

Наборы символов включают в себя не знающие символы. Сравнение (String, String) не учитывает такие символы, когда он выполняет культурно-чувствительное сравнение. Например, если следующий код выполняется на платформе .NET Framework 4 или более поздней версии, чувствительное к культуре сравнение "животного" с "ani-mal" (с использованием мягкого дефиса или U + 00AD) указывает что две строки эквивалентны.

Итак, похоже, что вы испытываете этот неосведомленный случай с персонажем. Если предположить, что символ - имеет очень маленький вес в сравнении, результаты сортировки выглядят так.

Первый случай:

660616280000
660616280100
6606162801000
6606162801040

Второй случай:

66061628000A
660616280100A
660616280104A
66061628010A 

Что имеет смысл