我注意到这个奇怪的问题.看看这个越南语(根据谷歌翻译)字符串:
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
这里发生了什么?!这怎么可能?
返回的结果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 = "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
,然后是要应用于final的组合急性重音(U + 0301)e
.line1
使用预组合字符为é
(U + 00E9),所以它的字节序列不匹配的sub
.
在现实世界的语义中,人们通常不会认为cafe
是子串café
; 的e
和e?
被视为不同的字符.这e?
恰好表示为一对字符开头,e
是编码方案(Unicode)的内部实现细节,不应影响结果.这是由上面的例子证明了对比café
和cafe?
; 除非特意打算进行序数(逐字节)比较,否则不会期望得到不同的结果.
根据您的示例调整此解释:
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比较评估为true(就像StringComparison.Ordinal
).但是,第15个代码单元line
是组合macron,◌̄(U + 0304),它与前面的?
(U + 1E73)组合给出??
.
这不是一个错误.该String.StartsWith
其实不仅仅是你的两个字符串的字符一个字符检查聪明得多.它考虑了您当前的文化(语言设置等),并考虑到收缩和特殊字符.(它不关心你最终需要两个字符??
.它将它比作一个).
因此,这意味着如果您不想采用所有这些特定于文化的设置,并且只想使用序数比较来检查它,则必须告诉比较器.
这是正确的方法(不要忽视案例,就像道格拉斯那样!):
line.StartsWith(sub, StringComparison.Ordinal);