为什么默认字符串比较器无法保持传递一致性
本文关键字:一致性 默认 字符串 比较器 为什么 | 更新日期: 2023-09-27 17:58:38
我知道这个问题以前或多或少已经被注意到了,但我仍然创建了这个新线程,因为我在编写单元测试时再次遇到了这个问题。
当字符串包含连字符(或减号,我说的是纯U+002D字符)时,默认的字符串比较(即我们对string.CompareTo(string)
、Comparer<string>.Default
、StringComparer.CurrentCulture
、string.Compare(string, string)
和其他字符串进行的区域性区分大小写的比较)违反了传递性。
这里有一个简单的复制:
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
对一个列表进行排序,就像在著名的不及物游戏中尝试对"石头"、"纸"answers"剪刀"的手进行排序一样。一项不可能完成的任务。
我上面代码示例的最后一部分显示,排序的结果取决于元素的初始顺序(并且列表中没有两个元素比较"相等"(0
))。
林克的listX.OrderBy(x => x)
当然也受到了影响。这应该是一个稳定的排序,但当对包含a
、b
和c
以及其他字符串的集合进行排序时,会得到奇怪的结果。
我在我的机器上用所有的CultureInfo
进行了尝试(因为这是一种依赖于区域性的排序),包括"不变区域性",每个都有相同的问题。我用试过这个。NET 4.5.1运行时,但我相信旧版本也有同样的错误。
结论:在中对字符串进行排序时。NET中,如果某些字符串包含连字符,则结果是不可预测的。
中引入了哪些更改。NET 4.0导致了这种行为
已经观察到,这种行为在不同版本的平台中是不一致的:in。NET 3.5中,带连字符的字符串可以可靠地排序。在所有版本的框架中,调用System.Globalization.CultureInfo.CurrentCulture.CompareInfo.GetSortKey
为这些字符串提供了唯一的DeyData
,那么为什么它们没有正确排序呢?
Microsoft Connect讨论以下是一些解决方法的代码:
static int CompareStringUsingSortKey(string s1, string s2)
{
SortKey sk1 = CultureInfo.InvariantCulture.CompareInfo.GetSortKey(s1);
SortKey sk2 = CultureInfo.InvariantCulture.CompareInfo.GetSortKey(s2);
return SortKey.Compare(sk1, sk2);
}