Почему средство сравнения строк по умолчанию не поддерживает транзитивную согласованность?
Я знаю эту проблемубыло отмечено ранееболее или менее кратко, но я все еще создаю этот новый поток, потому что я снова столкнулся с проблемой при написании модульного теста.
Сравнение строк по умолчанию (это зависимое от культуры сравнение с учетом регистра, которое мы получаем сstring.CompareTo(string)
, Comparer<string>.Default
, StringComparer.CurrentCulture
, string.Compare(string, string)
и другие) нарушает транзитивность, когда строки содержат дефисы (или минус, я говорю о простых символах U + 002D).
Вот простое воспроизведение:
static void Main()
{
const string a = "fk-";
const string b = "-fk";
const string c = "Fk";
Console.WriteLine(a.CompareTo(b)); // "-1"
Console.WriteLine(b.CompareTo(c)); // "-1"
Console.WriteLine(a.CompareTo(c)); // "1"
var listX = new List<string> { a, b, c, };
var listY = new List<string> { c, a, b, };
var listZ = new List<string> { b, c, a, };
listX.Sort();
listY.Sort();
listZ.Sort();
Console.WriteLine(listX.SequenceEqual(listY)); // "False"
Console.WriteLine(listY.SequenceEqual(listZ)); // "False"
Console.WriteLine(listX.SequenceEqual(listZ)); // "False"
}
В верхней части мы видим, как транзитивность терпит неудачу.a
меньше чемb
, а такжеb
меньше чемc
, ещеa
не может быть меньше, чемc
.
Это идет вразрез сдокументированное поведение сопоставления Unicode, в котором говорится, что:
... для любых строк A, B и C, если A <B и B <C, то A <C.
Теперь сортировка списка сa
, b
а такжеc
точно так же, как пытаться оценить руки«Скала», «Бумага» и «Ножницы» в известной непереходной игре. Невыполнимая задача.
Последняя часть моего примера кода выше показывает, что результат сортировки зависит от начального порядка элементов (и в списке нет двух элементов, которые сравнивают «равные» (0
)).
Linq-хlistX.OrderBy(x => x)
Это также влияет, конечно. Это должна быть стабильная сортировка, но вы получите странные результаты при заказе коллекции, содержащейa
, b
а такжеc
вместе с другими строками.
Я пробовал это свсе CultureInfo
s на моей машине (поскольку это зависит от культуры), включая «инвариантную культуру», и у каждого из них есть одна и та же проблема. Я пытался сделать это в среде выполнения .NET 4.5.1, но я считаю, что в старых версиях та же ошибка.
Вывод: при сортировке строк в .NET с помощью компаратора по умолчанию результаты непредсказуемы, если некоторые строки содержат дефисы.
Какие изменения были внесены в .NET 4.0, которые вызвали такое поведение?
Уже отмечалось, что это поведение несовместимо в разных версиях платформы: в .NET 3.5 строки с дефисами могут быть надежно отсортированы. Во всех версиях фреймворка звонитSystem.Globalization.CultureInfo.CurrentCulture.CompareInfo.GetSortKey
обеспечивает уникальныйDeyData
для этих строк, так почему они не отсортированы правильно?