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

时间:2014-04-15 15:21:19

标签: c# .net string sorting string-comparison

我知道这个问题has been noted before,或多或少简洁,但我仍然创建这个新线程,因为我在编写单元测试时再次遇到了这个问题。

默认字符串比较(即我们通过string.CompareTo(string)Comparer<string>.DefaultStringComparer.CurrentCulturestring.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小于bb小于c,但a未能小于c

这与Unicode排序规则的documented behavior相反,后者声明:

  

...对于任何字符串A,B和C,如果A&lt; B和B&lt; C,然后A&lt;下进行。

现在使用abc对列表进行排序,就像在众所周知的不及物游戏中对"Rock", "Paper" and "Scissors"的牌次进行排名一样。这是一项不可能完成的任务。

上面代码示例的最后一部分显示排序的结果取决于元素的初始顺序(并且列表中没有两个元素比较“等于”(0))。 / p> 当然,Linq的listX.OrderBy(x => x)也会受到影响。这应该是一个稳定的排序,但是在订购包含abc以及其他字符串的集合时会得到奇怪的结果。

我尝试使用所有我机器上的CultureInfo(因为这是一种依赖于文化的排序),包括“不变文化”,并且每个人都拥有同样的问题。我尝试使用.NET 4.5.1运行时,但我相信旧版本有相同的错误。

结论:使用默认比较器在.NET中对字符串进行排序时,如果某些字符串包含连字符,则结果是不可预测的。

导致此行为的.NET 4.0中引入了哪些更改?

已经观察到这种行为在不同版本的平台上不一致:在.NET 3.5中,带有连字符的字符串可以被可靠地排序。在框架的所有版本中,调用System.Globalization.CultureInfo.CurrentCulture.CompareInfo.GetSortKey为这些字符串提供唯一的DeyData,那么为什么它们不能正确排序?

1 个答案:

答案 0 :(得分:3)

Microsoft Connect Discussion 以下是一些解决方法:

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);
}