42

任何比较排序都要求底层的顺序运算符是传递的和反对称的。

在 .NET 中,某些字符串并非如此:

static void CompareBug()
{
  string x = "\u002D\u30A2";  // or just "-ア" if charset allows
  string y = "\u3042";        // or just "あ" if charset allows

  Console.WriteLine(x.CompareTo(y));  // positive one
  Console.WriteLine(y.CompareTo(x));  // positive one
  Console.WriteLine(StringComparer.InvariantCulture.Compare(x, y));  // positive one
  Console.WriteLine(StringComparer.InvariantCulture.Compare(y, x));  // positive one

  var ja = StringComparer.Create(new CultureInfo("ja-JP", false), false);
  Console.WriteLine(ja.Compare(x, y));  // positive one
  Console.WriteLine(ja.Compare(y, x));  // positive one
}

您会看到x严格大于y,并且y严格大于x

因为x.CompareTo(x)等等都给零(0),很明显这不是一个命令。毫不奇怪,当我在Sort数组或列表中包含x和之类的字符串时,我会得到不可预知的结果y。虽然我还没有对此进行测试,但如果字符串和用于键的字符串一样,我肯定SortedDictionary<string, WhatEver>会在保持排序顺序和/或定位项目时遇到问题。xy

这个错误是众所周知的吗?哪些版本的框架受到影响(我正在尝试使用 .NET 4.0)?

编辑:

这是一个符号为负的示例:

x = "\u4E00\u30A0";         // equiv: "一゠"
y = "\u4E00\u002D\u0041";   // equiv: "一-A"
4

2 回答 2

17

如果正确排序在您的问题中如此重要,只需使用序号字符串比较而不是文化敏感。只有这一个可以保证您想要的传递和反对称比较。

什么 MSDN 说:

在方法调用中指定 StringComparison.Ordinal 或 StringComparison.OrdinalIgnoreCase 值表示忽略自然语言特征的非语言比较。使用这些 StringComparison 值调用的方法基于简单的字节比较而不是由区域性参数化的大小写或等价表来确定字符串操作决策。在大多数情况下,这种方法最适合字符串的预期解释,同时使代码更快、更可靠。

它按预期工作:

    Console.WriteLine(String.Compare(x, y, StringComparison.Ordinal));  // -12309
    Console.WriteLine(String.Compare(y, x, StringComparison.Ordinal));  // 12309

是的,它没有解释为什么文化敏感的比较会给出不一致的结果。嗯,奇怪的文化——奇怪的结果。

于 2012-11-06T17:11:52.767 回答
1

在我发现原因是 .Net 40 及更高版本比较器(a1 < a2 和 a2 < a3,但 a1 > a3)。

我想弄清楚发生了什么的努力可以在这里找到:c# SortedList<string, TValue>.ContainsKey for successful added key 返回 false

您可能想查看我的 SO 问题的“更新 3”部分。似乎该问题已于 2012 年 12 月向 Microsoft 报告,并在 2013 年 1 月结束前以“无法修复”而关闭。此外,它还列出了可以使用的解决方法。

我创建了这个推荐的解决方法的实现,并验证它解决了我遇到的问题。我也刚刚验证这解决了您报告的问题。

public static void SO_13254153_Question()
{
    string x = "\u002D\u30A2";  // or just "-ア" if charset allows
    string y = "\u3042";        // or just "あ" if charset allows        

    var invariantComparer = new WorkAroundStringComparer();
    var japaneseComparer = new WorkAroundStringComparer(new System.Globalization.CultureInfo("ja-JP", false));
    Console.WriteLine(x.CompareTo(y));  // positive one
    Console.WriteLine(y.CompareTo(x));  // positive one
    Console.WriteLine(invariantComparer.Compare(x, y));  // negative one
    Console.WriteLine(invariantComparer.Compare(y, x));  // positive one
    Console.WriteLine(japaneseComparer.Compare(x, y));  // negative one
    Console.WriteLine(japaneseComparer.Compare(y, x));  // positive one
}

剩下的问题是这种解决方法太慢了,几乎不适合用于大型字符串集合。所以我希望微软会重新考虑关闭这个问题,或者有人知道更好的解决方法。

于 2013-07-12T23:02:08.093 回答