如何在C#中对连字字符折叠/归一化以实现字符串比较
字符串比较中的连字字符处理问题
问题背景
我最近在深挖字符串比较里的冷门细节,快陷进去了!想实现包含连字字符的字符串和规范非连字版本的等价比较——比如法语学习应用里,用户输入oeuf或œuf都能被识别为同一个词。我觉得这叫“折叠”,但不确定对不对。
我试过用NFKD归一化字符串,以为能把字符拆成组成部分,但只有部分Unicode码位支持分解,比如示例里的‘œ’就不行,这可把我难住了。
示例代码
using System.Text; using System.Globalization; // 该字符不支持分解 string str1 = "\u0153"; // œ (LATIN SMALL LIGATURE OE) string str2 = "oe"; string str1norm = str1.Normalize(NormalizationForm.FormKD); Console.WriteLine(str1norm.IsNormalized()); // True Console.WriteLine(str1norm.Equals(str2)); // False Console.WriteLine(str1norm.Length); // 1 // 该字符支持分解 str1 = "\uFB06"; // st (LATIN SMALL LIGATURE ST) str2 = "st"; str1norm = str1.Normalize(NormalizationForm.FormKD); Console.WriteLine(str1norm.IsNormalized()); // True Console.WriteLine(str1norm.Equals(str2)); // True // (未归一化比较)在大多数区域设置中为True,但并非全部(见下文) Console.WriteLine(str1.Equals(str2, StringComparison.CurrentCultureIgnoreCase)); Console.WriteLine(str1norm.Length); // 2
核心问题
既然只有部分连字可分解,我有三个问题:
- 不用手动遍历所有Unicode码位,怎么判断一个字符是否可分解?
- 有没有办法不用手动维护“不可分解字符-扩展形式”字典,就能实现字符串和规范版本的比较?我可不想挨个敲这些映射关系。
- (额外问题)为什么有的多字符连字(比如示例里的‘st’连字)能分解,有的却不行?像‘œ’这类字符特殊在哪?
补充研究
编辑: 我查了更多资料,像‘œ’或德语‘ß’这类字符被视为独立字符,而非组合字符。它们代表当前语言中的实际使用形式,哪怕历史上是由其他形式演变而来的。Unicode是基于当前使用情况定义的,所以把它们标记为不可分解是合理的。
区域设置的诡异现象
我统计了自己电脑上的区域设置情况:‘st’不用归一化就等于‘st’的有862个,只有7个区域设置会比较失败,分别是:
aa(阿法尔语)aa-DJ(阿法尔语-吉布提)aa-ER(阿法尔语-厄立特里亚)aa-ET(阿法尔语-埃塞俄比亚)en-US-POSIX(美式英语POSIX设置)ssy(萨霍语)ssy-ER(萨霍语-厄立特里亚)
这事挺有意思,但这种差异实在让人摸不着头脑。我知道阿法尔语和萨霍语用的是拉丁字母书写系统,可能它们的Unicode代码页不太常用,缺了其他区域设置里有的连字映射条目。
Invariant文化的奇怪行为
最后还有个困惑:单字符‘st’和它的分解形式‘st’在.NET的Invariant文化下,只有忽略大小写时才相等,但明明两个字符串都是小写的:
//...接之前的代码... Console.WriteLine(str1.Equals(str2, StringComparison.InvariantCulture)); // False Console.WriteLine(str1.Equals(str2, StringComparison.InvariantCultureIgnoreCase)); // True
我知道有人会说“归一化之后就别纠结了”,但我就是想搞清楚这到底是为什么。
内容的提问来源于stack exchange,提问作者Dave R.
相关产品推荐
相关产品推荐

