String.StartsWith不使用亚洲语言

本文关键字:语言 StartsWith String | 更新日期: 2023-09-27 18:30:06

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

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函数,它将字符串char逐个比较

public bool CustomStartWith(string parent, string child)
{
    for (int i = 0; i < child.Length; i++)
    {
        if (parent[i] != child[i])
            return false;
    }
    return true;
}

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

CustomStartWith("Mìng-dĕ̤ng-ngṳ̄", "Mìng-dĕ̤ng-ngṳ")
true

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

String.StartsWith不使用亚洲语言

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

如果您真的想要将字符串作为纯字节序列进行比较,请使用重载:

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

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

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

这里有一个更熟悉的例子:

var line1 = "café";   // 63 61 66 E9     – precomposed character 'é' (U+00E9)
var line2 = "café";   // 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的字节序列不匹配。

在现实世界的语义中,通常不会将cafe视为café的子串;CCD_ 13和CCD_。恰好表示为一对以e开头的字符,这是编码方案(Unicode)的内部实现细节,不应影响结果。这通过对比CCD_ 17和CCD_;除非特别打算进行有序(逐字节)比较,否则不会期望不同的结果。

将此解释适用于您的示例:

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个代码单元是相同的,这就是为什么逐字符比较的结果为true(就像StringComparison.Ordinal一样)。而CCD_ 20中的第15个编码单元是组合宏码,◌̄(U+0304),它与它前面的(U+1E73)结合得到ṳ̄

这不是一个bug。事实上,String.StartsWith比对两个字符串逐个字符进行检查要聪明得多。它考虑了你当前的文化(语言设置等),并考虑了缩写和特殊字符。(它不在乎你需要两个字符才能得到ṳ̄。它将其作为一个字符进行比较)。

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

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

line.StartsWith(sub, StringComparison.Ordinal);