为什么默认字符串比较器无法保持传递一致性

本文关键字:一致性 默认 字符串 比较器 为什么 | 更新日期: 2023-09-27 17:58:38

我知道这个问题以前或多或少已经被注意到了,但我仍然创建了这个新线程,因为我在编写单元测试时再次遇到了这个问题。

当字符串包含连字符(或减号,我说的是纯U+002D字符)时,默认的字符串比较(即我们对string.CompareTo(string)Comparer<string>.DefaultStringComparer.CurrentCulturestring.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小于bb小于c,但a不小于c

这违背了Unicode排序规则的记录行为,即:

对于任何串A、B和C,如果A<B和B<C、 则A<C.

现在用abc对一个列表进行排序,就像在著名的不及物游戏中尝试对"石头"、"纸"answers"剪刀"的手进行排序一样。一项不可能完成的任务。

我上面代码示例的最后一部分显示,排序的结果取决于元素的初始顺序(并且列表中没有两个元素比较"相等"(0))。

林克的listX.OrderBy(x => x)当然也受到了影响。这应该是一个稳定的排序,但当对包含abc以及其他字符串的集合进行排序时,会得到奇怪的结果。

我在我的机器上用所有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);
}