- 开发工具
【免费下载链接】Humanizer
Humanizer meets all your .NET needs for manipulating and displaying strings, enums, dates, times, timespans, numbers and quantities
Humanizer 是面向 .NET 的字符串、枚举、日期与数字人性化处理库,其复数/单数变换能力由词汇表(Vocabulary)与曲折变化(Inflector)机制共同支撑。本文围绕Humanizer.Plurality枚举展开,说明它如何作为"复数性提示"贯穿Pluralize/Singularize的调用语义,并结合 Plurality.cs 源码与 InflectorTests.cs 测试用例,讲清三态枚举背后的实现原理与实战用法。读完本文,你将掌握在何种场景下向 Humanizer 传递"已知/未知复数性"提示,以及如何避免双重复数化与误单数化。
一、Plurality 枚举:三态复数性提示契约
Plurality是定义在Humanizer命名空间下的公开枚举,其作用在 XML 注释中描述得非常明确:为 Humanizer 提供关于一个单词是单数、复数还是复数性未知的提示("Provides hint for Humanizer as to whether a word is singular, plural or with unknown plurality")。
该枚举定义于 src/Humanizer/Plurality.cs,完整成员如下:
| 成员 | 数值 | 语义(来自 XML 注释) |
|---|---|---|
Singular | 0 | 该单词是单数(The word is singular) |
Plural | 1 | 该单词是复数(The word is plural) |
CouldBeEither | 2 | 无法确定其复数性(I am unsure of the plurality) |
对应的 API 参考文档位于 website/versioned_docs/version-3.0.1/api/Humanizer.Plurality.md,三个成员与上述定义一一对应。
从枚举的数值设计看,Singular = 0、Plural = 1、CouldBeEither = 2的连续递增编号暗示了"确定性递减"的语义层级:从明确单数、明确复数,到无法判断。这种三态设计在自然语言处理中很常见——很多英文单词(如 "series"、"fish" 这类不可数/零复数词)从形态上无法直接断定其单复数,需要显式声明"不确定"状态。
二、Plurality 如何进入 Humanizer 的 API 语义
值得说明的是,Plurality枚举本身在公开 API 中并未直接作为方法参数出现,但它的三态语义被完整映射到了InflectorExtensions的布尔参数上,从源码结构看可以这样对应理解:
- 已知单数(
Singular)→ 调用Pluralize(word, inputIsKnownToBeSingular: true); - 已知复数(
Plural)→ 调用Singularize(word, inputIsKnownToBePlural: true); - 复数性未知(
CouldBeEither)→ 将对应参数传false,Humanizer 会进入"检查所有可能性"的防御式处理分支。
入口扩展方法定义在 src/Humanizer/InflectorExtensions.cs:
[return: NotNullIfNotNull(nameof(word))] public static string? Pluralize(this string? word, bool inputIsKnownToBeSingular = true) => Vocabularies.Default.Pluralize(word, inputIsKnownToBeSingular); public static string Singularize(this string word, bool inputIsKnownToBePlural = true, bool skipSimpleWords = false) => Vocabularies.Default.Singularize(word, inputIsKnownToBePlural, skipSimpleWords);两个参数的含义与默认值如下:
| 参数 | 默认值 | 含义 |
|---|---|---|
inputIsKnownToBeSingular | true | 调用者确定输入是单数时保持默认;若输入可能是复数(避免双重复数化),传false |
inputIsKnownToBePlural | true | 调用者确定输入是复数时保持默认;若输入可能是单数(避免错误单数化),传false |
skipSimpleWords | false | 为true时跳过对仅以字母 s 结尾的简单单词的单数化,避免把 "ross" 误变成 "ros" |
从调用关系看,扩展方法将参数原样透传给Vocabularies.Default.Pluralize/Singularize(见 src/Humanizer/Inflections/Vocabularies.cs),而真正消费"复数性提示"的是 Vocabulary 内部实现。
三、源码级原理:未知复数性时的"双向检查"防御逻辑
Plurality的CouldBeEither状态在Vocabulary.Pluralize/Singularize中对应着一套严谨的回退逻辑。以单数化为例,Vocabulary.cs 中的实现为:
if (inputIsKnownToBePlural) { return result ?? word; } // the Plurality is unknown so we should check all possibilities var asPlural = ApplyRules(plurals, word, false); if (asPlural == word || string.Equals(word + "s", asPlural, StringComparison.OrdinalIgnoreCase)) { return result ?? word; } var asPluralAsSingular = ApplyRules(singulars, asPlural, false); if (asPluralAsSingular != word || result == word) { return result ?? word; } return word;这段代码的执行路径可以拆解为:
- 已知复数(
inputIsKnownToBePlural: true):直接应用单数化规则,规则无匹配时原样返回单词,不做额外猜测; - 未知复数(
Plurality的CouldBeEither场景):先把单词当复数规则跑一遍得到asPlural,若asPlural与原词相同或仅是加了个 "s" 的简单形式,说明它本来就像复数,直接返回规则单数化结果; - 兜底校验:再对
asPlural反向做一次单数化得到asPluralAsSingular,若反向结果能还原为原词且直接单数化结果与原词不同,才返回直接结果;否则保守地返回原词。
Pluralize侧有对称的逻辑(Vocabulary.cs):当inputIsKnownToBeSingular: false时,会先把输入按单数规则处理再按复数规则反向验证,若"单数化后又复数化"能回到原词、且直接复数化结果与原词不同,则返回原词以规避双重复数化。
Vocabulary还内置了三条预处理/后处理路径,与上述防御逻辑协同:
- 纯字母 s 处理:
LetterS方法(正则^([sS])[sS]*$)把像 "s"、"SS" 这样的输入直接追加 "s",避免被普通规则误伤; - 复合词头部处理:
CompoundHeadLength检测以 "per" 分隔的复合词(如 "meter per second"),只对头部做变格,保留分母不变; - 不可数词短路:
IsUncountable命中 "fish" 这类词时直接原样返回(wholeWordMatch = true),不参与任何规则匹配。
四、默认词汇表:规则、不规则词与不可数词的注册体系
复数性判断最终落到Vocabularies.BuildDefault()构建的默认词汇表上(src/Humanizer/Inflections/Vocabularies.cs),它由三类注册方法组成,对应Vocabulary的公开 API:
| 方法 | 用途 | 默认表示例 |
|---|---|---|
AddPlural(rule, replacement) | 注册复数化正则规则 | (x\|ch\|ss\|sh)$→$1es("box" → "boxes");([^aeiouy]\|qu)y$→$1ies("city" → "cities") |
AddSingular(rule, replacement) | 注册单数化正则规则 | s$→"";(vert\|ind)ices$→$1ex("vertices" → "vertex") |
AddIrregular(singular, plural, matchEnding) | 注册不规则词对 | person/people、child/children、foot/feet、goose/geese |
AddUncountable(word) | 注册不可数词 | fish、personnel(复数形式与单数相同) |
AddIrregular的matchEnding参数值得注意:为true(默认)时生成(x)xx$结尾匹配规则,允许在长词末尾生效;为false时使用^singular$/^plural$精确匹配,只作用于独立单词。默认表中 "olive"、"ex"、"is"、"was"、"that"、"this"、"bus"、"die"、"tie" 等词均以matchEnding: false注册,避免对 "olives" 以外的词造成误伤。
AddAcronym方法则负责维护缩写词的大小写(如 "HTML"),确保变格过程中不破坏缩写形态。需要说明的是,Vocabulary目前仅支持单一默认词汇表(Vocabularies.Default),不支持多词汇表并行或删除已注册规则(见 Vocabulary.cs 的类注释)。
五、测试验证:三态提示如何被真实场景覆盖
InflectorTests.cs 中的测试用例直接验证了"已知/未知复数性"四象限的幂等性。以InflectionsPreserveAllCaps为例(第 35-47 行):
[InlineData("SINGULAR TYPE NAME", "SINGULAR TYPE NAMES")] [InlineData("BUS", "BUSES")] [InlineData("PERSON", "PEOPLE")] public void InflectionsPreserveAllCaps(string singular, string plural) { Assert.Equal(plural, singular.Pluralize()); Assert.Equal(singular, plural.Singularize()); Assert.Equal(plural, singular.Pluralize(inputIsKnownToBeSingular: false)); Assert.Equal(plural, plural.Pluralize(inputIsKnownToBeSingular: false)); Assert.Equal(singular, singular.Singularize(inputIsKnownToBePlural: false)); Assert.Equal(singular, plural.Singularize(inputIsKnownToBePlural: false)); }该测试同时验证了三种关键行为:
- 全大写保留:无论输入是 "BUS" 还是 "PERSON",变换后保持全大写形态;
- 未知复数性下的幂等:
plural.Pluralize(inputIsKnownToBeSingular: false)仍返回 "BUSES"/"PEOPLE" 而不是 "BUSESES",证明未知场景不会双重复数化; - 未知单数性下的幂等:
singular.Singularize(inputIsKnownToBePlural: false)仍返回 "BUS"/"PERSON",不会错误地去尾 s。
InflectsCompoundRates(第 49-70 行)则覆盖了 "meter per second" → "meters per second"、"foot per second" → "feet per second" 等复合词场景,且同样在四种已知/未知组合下断言幂等。与之相对的DoesNotTreatUnstructuredPhrasesAsCompoundRates(第 72-80 行)验证了 "as per request"、"meter/per/second" 这类非结构化短语不会被误当作 per 复合词处理。
此外,skipSimpleWords: true在测试第 137 行被用于验证不会把仅以 s 结尾的简单词错误单数化(如 "ross"),这与Singularize参数文档中的说明一致(InflectorExtensions.cs)。
六、实战建议:何时使用何种复数性提示
综合枚举语义与源码实现,可以给出如下使用准则:
- 数据来自受控词汇(枚举名、类型名、业务字典):直接使用默认值(
inputIsKnownToBeSingular: true/inputIsKnownToBePlural: true),此时 Humanizer 按已知状态走最短路径,性能最优; - 数据来自用户输入或自由文本(对应
CouldBeEither):将提示参数显式传false,让 Humanizer 进入"双向检查"防御逻辑。例如把一段可能已经复数化的文本交给Pluralize前,传inputIsKnownToBeSingular: false可避免 "cats" → "catses"; - 处理纯以 s 结尾的人名/专名:单数化时配合
skipSimpleWords: true,避免 "ross" → "ros" 的误变; - 依赖默认词汇表能力边界:
Vocabulary面向美式英语(US English)设计,不规则词、不可数词与正则规则均内置于Vocabularies.Default;如需覆盖新词,可在进程启动时通过AddIrregular、AddUncountable、AddPlural等公开方法向默认表补充规则。
Plurality枚举的价值在于它把"单词复数性"这一模糊概念显式化为三态契约:Singular、Plural与CouldBeEither。在 Humanizer 的公开 API 中,该契约通过inputIsKnownToBeSingular/inputIsKnownToBePlural两个布尔参数落地;在Vocabulary内部,它则转化为一套以"检查所有可能性"为核心的双向规则验证算法。理解这三态语义,就能在字符串人性化处理中精准规避双重复数化与误单数化这两类最常见的边界问题。
- 开发工具
【免费下载链接】Humanizer
Humanizer meets all your .NET needs for manipulating and displaying strings, enums, dates, times, timespans, numbers and quantities
相关推荐
Humanizer 的 Plurality 枚举:单数、复数与未知词性提示的语义与底层实现解析
Humanizer 的 Plurality 枚举:单数、复数与未知词性提示的语义与底层实现解析 Plurality 是 Humanizer 词形变化(Infle
开发工具Humanizer 中的 Plurality 枚举:单复数语义提示与屈折变换的底层支撑
Humanizer 中的 Plurality 枚举:单复数语义提示与屈折变换的底层支撑 导读 Plurality 是 Humanizer 中一个轻量但关键的枚举
开发工具Humanizer 复数提示枚举 Plurality 深度解析:单复数判定、歧义处理与词形变换实战
Humanizer 复数提示枚举 Plurality 深度解析:单复数判定、歧义处理与词形变换实战 导读 Plurality 是 Humanizer 公开 AP
开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考