Ошибка в сравнении строк .NET Framework
Это требование для любого сортировки сортировки для работы, что основной оператор порядка транзитивный и антисимметричный.
В .NET это неверно для некоторых строк:
static void CompareBug()
{
string x = "\u002D\u30A2"; // or just "-ア" if charset allows
string y = "\u3042"; // or just "あ" if charset allows
Console.WriteLine(x.CompareTo(y)); // positive one
Console.WriteLine(y.CompareTo(x)); // positive one
Console.WriteLine(StringComparer.InvariantCulture.Compare(x, y)); // positive one
Console.WriteLine(StringComparer.InvariantCulture.Compare(y, x)); // positive one
var ja = StringComparer.Create(new CultureInfo("ja-JP", false), false);
Console.WriteLine(ja.Compare(x, y)); // positive one
Console.WriteLine(ja.Compare(y, x)); // positive one
}
Вы видите, что x
строго больше y
, а y
строго больше x
.
Потому что x.CompareTo(x)
и т.д. все дают нуль (0
), ясно, что это не порядок. Неудивительно, что я получаю непредсказуемые результаты, когда я Sort
массивы или списки, содержащие строки типа x
и y
. Хотя я не тестировал это, я уверен, что SortedDictionary<string, WhatEver>
будет иметь проблемы, сохраняя себя в отсортированном порядке и/или размещая элементы, если для ключей используются строки типа x
и y
.
Известна ли эта ошибка? Какие версии фреймворка затронуты (я пытаюсь это сделать с .NET 4.0)?
EDIT:
Здесь пример, где знак отрицателен в любом случае:
x = "\u4E00\u30A0"; // equiv: "一゠"
y = "\u4E00\u002D\u0041"; // equiv: "一-A"
Ответы
Ответ 1
Я столкнулся с этим сообщением SO, в то время как я пытался выяснить, почему у меня возникли проблемы с извлечением (строковыми) ключами, которые были вставлены в SortedList, после того как я обнаружил, что причиной было нечетное поведение .Net 40 и выше (a1 < a2 и a2 < a3, но a1 > a3).
Моя борьба за то, что происходит, можно найти здесь: С# SortedList < string, TValue > .ContainsKey для успешно добавленного ключа возвращает false.
Возможно, вы захотите посмотреть раздел "ОБНОВЛЕНИЕ 3" моего вопроса SO. Похоже, что проблема была отправлена Microsoft в декабре 2012 года и закрыта до конца января 2013 года, поскольку "не будет исправлена". Кроме того, в нем приведено обходное решение, которое может быть использовано.
Я создал реализацию этого рекомендуемого обходного пути и проверил, что он исправил проблему, с которой я столкнулся. Я также просто подтвердил, что это устраняет проблему, о которой вы сообщали.
public static void SO_13254153_Question()
{
string x = "\u002D\u30A2"; // or just "-ア" if charset allows
string y = "\u3042"; // or just "あ" if charset allows
var invariantComparer = new WorkAroundStringComparer();
var japaneseComparer = new WorkAroundStringComparer(new System.Globalization.CultureInfo("ja-JP", false));
Console.WriteLine(x.CompareTo(y)); // positive one
Console.WriteLine(y.CompareTo(x)); // positive one
Console.WriteLine(invariantComparer.Compare(x, y)); // negative one
Console.WriteLine(invariantComparer.Compare(y, x)); // positive one
Console.WriteLine(japaneseComparer.Compare(x, y)); // negative one
Console.WriteLine(japaneseComparer.Compare(y, x)); // positive one
}
Оставшаяся проблема заключается в том, что это обходное решение настолько медленное, что практически невозможно использовать с большими наборами строк. Поэтому я надеюсь, что Microsoft передумает закрыть эту проблему или что кто-то знает об улучшении обхода.
Ответ 2
Если правильная сортировка настолько важна в вашей проблеме, просто используйте порядковый сравнение строк вместо чувствительной к культуре. Только это гарантирует переходное и антисимметричное сравнение, которое вы хотите.
Что MSDN говорит:
Указание параметра StringComparison.Ordinal или Значение StringComparison.OrdinalIgnoreCase в вызове метода означает нелингвистическое сравнение, в котором особенности естественных языков игнорируются. Методы, вызываемые с помощью этих StringComparison значения основанные на строковых операционных решениях при сравнении простых байтов вместо таблиц обтекания или эквивалентности, которые параметризуются культура. В большинстве случаев этот подход лучше всего подходит для интерпретация строк, делая код быстрее и надежнее.
И он работает как ожидалось:
Console.WriteLine(String.Compare(x, y, StringComparison.Ordinal)); // -12309
Console.WriteLine(String.Compare(y, x, StringComparison.Ordinal)); // 12309
Да, это не объясняет, почему культурно-чувствительное сравнение дает непоследовательные результаты. Ну, странная культура - странный результат.