我注意到这个奇怪的问题。看看这个越南语(根据Google Translate)字符串:
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-by-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
这里发生了什么?!这怎么可能?
答案 0 :(得分:37)
StartsWith
返回的结果是正确的。默认情况下,大多数字符串比较方法使用当前区域性而不是纯字节序列执行区分文化的比较。虽然您的line
以与sub
相同的字节序列开头,但它所代表的子字符串在大多数(或所有)文化下并不相同。
如果您真的想要将字符串视为普通字节序列的比较,请使用overload:
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
将precomposed character用于é
(U + 00E9),因此其字节序列与sub
的字节序列不匹配。
在现实世界的语义中,通常不会将cafe
视为café
的子字符串; e
和é
被视为不同的字符。恰好将é
表示为以e
开头的一对字符是编码方案(Unicode)的内部实现细节,不应影响结果。上述示例对比了café
和café
;除非特意打算进行序数(逐字节)比较,否则我们不会期望得到不同的结果。
根据您的示例调整此解释:
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个代码单元是相同的,这就是为什么你的char-by-char比较评估为真(就像StringComparison.Ordinal
)。但是,line
中的第15个代码单元是组合macron,◌̄(U+0304),它与之前的ṳ
(U+1E73)结合使用ṳ̄
}。
答案 1 :(得分:9)
这不是错误。 String.StartsWith
实际上比两个字符串的逐个字符检查要聪明得多。它考虑了您当前的文化(语言设置等),并考虑到收缩和特殊字符。 (它不关心你需要两个字符以ṳ̄
结尾。它将它比作一个。)
所以这意味着如果您不想采用所有这些特定于文化的设置,并且只想使用序数比较来检查它,您必须告诉比较器。
这是正确的方法(不要像Douglas那样忽略这种情况!):
line.StartsWith(sub, StringComparison.Ordinal);