我知道这个问题之前已经注意到 https://stackoverflow.com/questions/9354966/string-sorting-issue-in-c-sharp/9355086#9355086,或多或少简洁,但我仍然创建这个新线程,因为我在编写单元测试时再次遇到了这个问题。
默认字符串比较(即我们使用的依赖于区域性的区分大小写的比较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
, and b
小于c
, yet a
未能小于c
.
这违背了记录的行为 http://www.unicode.org/faq/collation.html#5Unicode 排序规则指出:
...对于任何字符串 A、B 和 C,如果 A
现在对列表进行排序a
, b
and c
就像试图对双手进行排名一样“石头”、“布”、“剪刀” http://en.wikipedia.org/wiki/Rock-paper-scissors在著名的不及物游戏中。这是一项不可能完成的任务。
上面代码示例的最后一部分显示排序结果取决于元素的初始顺序(并且列表中没有两个元素比较“相等”(0
)).
Linq's listX.OrderBy(x => x)
当然也受到影响。这应该是一个稳定的排序,但是当排序包含以下内容的集合时,您会得到奇怪的结果a
, b
and c
与其他字符串一起。
我尝试过这个all the CultureInfo
在我的机器上(因为这是一种依赖于文化的类型),包括“不变的文化”,并且每个人都有相同的问题。我在 .NET 4.5.1 运行时尝试过此操作,但我相信旧版本也有相同的错误。
结论:当使用默认比较器对 .NET 中的字符串进行排序时,如果某些字符串包含连字符,则结果是不可预测的。
.NET 4.0 中引入了哪些更改导致了此行为?
据观察,这种行为在不同版本的平台上是不一致的:在 .NET 3.5 中,带有连字符的字符串可以可靠地排序。在框架的所有版本中,调用System.Globalization.CultureInfo.CurrentCulture.CompareInfo.GetSortKey
提供独特的DeyData
对于这些字符串,为什么它们没有正确排序呢?