关于C#:String.StartsWith不使用亚洲语言?

String.StartsWith not working with Asian languages?

我注意到这个奇怪的问题。查看这个越南语(根据谷歌翻译)字符串:

1
2
3
4
5
6
7
8
string line ="Mìng-d??ng-ng??";
string sub ="Mìng-d??ng-ng?";
line.Length
15
sub.Length
14
line.StartsWith(sub)
false

在我看来这是个错误的结果。所以,我实现了我的自定义StartWith函数,它将字符串逐字符进行比较。

1
2
3
4
5
6
7
8
9
public bool CustomStartWith(string parent, string child)
{
    for (int i = 0; i < child.Length; i++)
    {
        if (parent[i] != child[i])
            return false;
    }
    return true;
}

正如我假设的,运行这个函数的结果

1
2
CustomStartWith("Mìng-d??ng-ng??","Mìng-d??ng-ng?")
true

这是怎么回事?!这怎么可能?


StartsWith返回的结果是正确的。默认情况下,大多数字符串比较方法使用当前区域性而不是纯字节序列执行区域性敏感的比较。虽然您的line以与sub相同的字节序列开头,但它所代表的子串在大多数(或所有)文化中并不相等。

如果确实需要将字符串视为纯字节序列的比较,请使用重载:

1
line.StartsWith(sub, StringComparison.Ordinal);                       // true

如果希望比较不区分大小写:

1
line.StartsWith(sub, StringComparison.OrdinalIgnoreCase);             // true

下面是一个更熟悉的例子:

1
2
3
4
5
6
7
8
var line1 ="café";   // 63 61 66 E9     – precomposed character 'é' (U+00E9)
var line2 ="cafe?";   // 63 61 66 65 301 – base letter e (U+0065) and
                      //                   combining acute accent (U+0301)
var sub   ="cafe";   // 63 61 66 65
Console.WriteLine(line1.StartsWith(sub));                             // false
Console.WriteLine(line2.StartsWith(sub));                             // false
Console.WriteLine(line1.StartsWith(sub, StringComparison.Ordinal));   // false
Console.WriteLine(line2.StartsWith(sub, StringComparison.Ordinal));   // true

在上述示例中,line2以与sub相同的字节序列开始,然后是要应用于最终e的组合锐音符(U+0301)。line1使用é的预编译字符(u+00e9),因此其字节序列与sub的字节序列不匹配。

在现实语义学中,人们通常不认为cafecafé的子串;ee?被视为不同的字符。e?恰好表示为以e开头的一对字符,这是编码方案(unicode)的内部实现细节,不应影响结果。上面的例子对比了cafécafe?,证明了这一点;除非特别打算进行有序(逐字节)比较,否则人们不会期望得到不同的结果。

根据您的示例调整此解释:

1
2
string line ="Mìng-d??ng-ng??";   // 4D EC 6E 67 2D 64 115 324 6E 67 2D 6E 67 1E73 304
string sub  ="Mìng-d??ng-ng?";   // 4D EC 6E 67 2D 64 115 324 6E 67 2D 6E 67 1E73

每个.NET字符代表一个UTF-16代码单元,其值显示在上面的注释中。前14个代码单元是相同的,这就是为什么逐字符比较的结果是真的(就像StringComparison.Ordinal一样)。但是,line中的第15个代码单元是组合宏,??(U+0304),与前面的?(U+1E73)组合,得到??


这不是错误。事实上,String.StartsWith比对两个字符串逐个字符进行检查更聪明。它考虑到您当前的文化(语言设置等),并考虑到收缩和特殊字符。(您不需要两个字符就可以以??结尾)。它把它比作一个)。

所以这意味着如果你不想使用所有这些特定于区域性的设置,只想使用顺序比较来检查它,你必须告诉比较器这一点。

这是正确的方法(不要像道格拉斯那样忽视这个案例!):

1
line.StartsWith(sub, StringComparison.Ordinal);