Я знаю, что эта проблема была отмечена до, более или менее сжатой, но я все же создаю этот новый поток, потому что снова столкнулся с проблемой при написании unit test.
Сравнение строк по умолчанию (то есть сопоставимое с культурой сравнение с регистром, которое мы получаем с 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 < С.
Теперь сортировка списка с помощью a
, b
и c
в точности аналогична попытке присвоить руки "Rock" , "Paper" и "Ножницы" в известной непереходной игре. Невозможная задача.
Последняя часть моего примера кода показывает, что результат сортировки зависит от начального порядка элементов (и в списке нет двух элементов, которые сравнивают "равно" (0
)).
Linq listX.OrderBy(x => x)
также зависит, конечно. Это должно быть стабильным, но вы получаете странные результаты при заказе коллекции, содержащей a
, b
и c
вместе с другими строками.
Я попробовал это со всеми CultureInfo
на моей машине (так как это зависит от культуры), включая "инвариантную культуру", и каждая из них имеет ту же проблему. Я пробовал это с .NET 4.5.1, но я считаю, что более старые версии имеют одинаковую ошибку.
Заключение: при сортировке строк в .NET с помощью сравнения по умолчанию результаты непредсказуемы, если в некоторых строках содержатся дефисы.
Какие изменения были внесены в .NET 4.0, вызвавшие это поведение?
Уже было замечено, что это поведение непоследовательно в разных версиях платформы: в .NET 3.5 строки с дефисами могут быть надежно отсортированы. Во всех версиях структуры вызов System.Globalization.CultureInfo.CurrentCulture.CompareInfo.GetSortKey
предоставляет уникальные DeyData
для этих строк, поэтому почему они не отсортированы правильно?