- 开发工具
【免费下载链接】Humanizer
Humanizer meets all your .NET needs for manipulating and displaying strings, enums, dates, times, timespans, numbers and quantities
本篇技术指南以 Humanizer 库 OrdinalizeExtensions 的 API 参考文档为主体,系统讲解如何将数字转换为序数形式(如1st、2nd、3rd),深入剖析其在当前仓库中的源码实现、语法性别(GrammaticalGender)支持、区域性(CultureInfo)本地化机制以及 64 位整数扩展。读完本文,你将掌握Ordinalize全系列重载的用法、底层IOrdinalizer插件化架构,以及如何利用它输出符合巴西葡萄牙语、西班牙语等语言习惯的序数文本。
什么是序数化(Ordinalize)
序数(ordinal number)用于表示在有序序列中的位置,例如英文中的1st、2nd、3rd、4th。Humanizer 的OrdinalizeExtensions静态类为int、long和string类型提供了同名的扩展方法Ordinalize,用于"把一个数字变成序数字符串"。它是 Humanizer 众多人性化字符串功能(Humanize、Dehumanize、ToQuantity等)中的一员,位于 src/Humanizer/OrdinalizeExtensions.cs。
public static class OrdinalizeExtensions { public static string Ordinalize(this int number); public static string Ordinalize(this string numberString); // ... 更多重载 }根据 src/Humanizer/OrdinalizeExtensions.cs 中的类注释,序数化只接受整数值;如果调用方持有小数,必须先自行选择并执行显式的取整与类型转换策略,再调用Ordinalize。
基础用法:int 与 string 两个入口
原 API 文档列出两类接收者(receiver)的Ordinalize重载:int与string。两者在语义上等价——将数字转为序数文本,区别仅在于输入类型。
| 方法签名 | 参数说明 | 返回 |
|---|---|---|
string Ordinalize(this int number) | number:要被序数化的整数 | string |
string Ordinalize(this string numberString) | numberString:以字符串形式表示的数字 | string |
从源码看,int重载的默认实现(OrdinalizeExtensions.cs)会委托给带CultureInfo的重载:
public static string Ordinalize(this int number) => number.Ordinalize(CultureInfo.CurrentCulture);而string重载(OrdinalizeExtensions.cs)则直接调用全局默认序数化器:
public static string Ordinalize(this string numberString) => Configurator.Ordinalizer.Convert(int.Parse(numberString), NormalizeOrdinalNumberString(numberString));即先通过int.Parse把字符串解析为整数,再交给配置好的IOrdinalizer完成转换。在英文环境下,实际输出由英语序数化器(基于ModuloSuffixOrdinalizer的取模后缀规则)决定:
1.Ordinalize(); // "1st" 2.Ordinalize(); // "2nd" 3.Ordinalize(); // "3rd" 4.Ordinalize(); // "4th" 11.Ordinalize(); // "11th" —— 注意不是 "11st" 21.Ordinalize(); // "21st" 101.Ordinalize(); // "101st"这些结果与测试用例 tests/Humanizer.Tests/OrdinalizeTests.cs 中的[InlineData]断言完全一致,例如"1" → "1st"、"11" → "11th"、"21" → "21st"、"101" → "101st"。
语法性别(GrammaticalGender)参数
原文档明确指出,Ordinalize的多个重载接受Humanizer.GrammaticalGender参数,并特别强调其用于**巴西葡萄牙语(Brazilian Portuguese)**区域:
1.Ordinalize(GrammaticalGender.Masculine) -> "1º" 1.Ordinalize(GrammaticalGender.Feminine) -> "1ª"GrammaticalGender是一个三值枚举(src/Humanizer/GrammaticalGender.cs):
Masculine(阳性)Feminine(阴性)Neuter(中性)
// 巴西葡萄牙语 1.Ordinalize(GrammaticalGender.Masculine); // "1º" 1.Ordinalize(GrammaticalGender.Feminine); // "1ª" "1".Ordinalize(GrammaticalGender.Masculine); // "1º" "1".Ordinalize(GrammaticalGender.Feminine); // "1ª"源码中性别相关的重载(OrdinalizeExtensions.cs)会将参数透传给IOrdinalizer.Convert(int, string, GrammaticalGender);而巴西葡萄牙语的序数化器正是基于 SuffixOrdinalizer 实现——该类接收三个性别后缀,并在Convert中按性别选择后缀(SuffixOrdinalizer.cs):
return numberString + gender switch { GrammaticalGender.Feminine => feminineSuffix, // "ª" GrammaticalGender.Neuter => neuterSuffix, _ => masculineSuffix // "º" };CultureInfo 参数与本地化机制
原文档中多个重载接受System.Globalization.CultureInfo culture参数,其语义为:指定用于序数化的区域性;若传入null,则使用当前线程的 UI 文化(current thread's UI culture)。
从实现看,culture为null时实际回落逻辑(OrdinalizeExtensions.cs)为:
var resolvedCulture = culture ?? CultureInfo.CurrentCulture; return Configurator.Ordinalizers.ResolveForCulture(culture) .Convert(ParseOrdinalNumber(numberString, resolvedCulture), ...);这里的关键是Configurator.Ordinalizers—— 一个LocaliserRegistry<IOrdinalizer>类型的注册表(src/Humanizer/Configuration/Configurator.cs)。它由 OrdinalizerRegistry 构建:默认回退到DefaultOrdinalizer(原样返回输入文本),随后通过源代码生成器(Source Generator)按 locale 注册各语言的专用序数化器。
DefaultOrdinalizer(src/Humanizer/Localisation/Ordinalizers/DefaultOrdinalizer.cs)本身是一个"什么都不做"的基类:
public virtual string Convert(long number, string numberString) => numberString;各区域语言通过继承它来覆盖行为。从 OrdinalizerProfileCatalogInput.cs 可见,源生成器支持三种序数化器类型:
suffix:固定性别后缀(如巴西葡萄牙语的º/ª),对应SuffixOrdinalizer;modulo-suffix:按数字取模规则选择后缀(如英语的st/nd/rd/th),对应 ModuloSuffixOrdinalizer;number-word-suffix:基于数字单词的后缀形式。
以英语为例,ModuloSuffixOrdinalizer的后缀选择遵循严格优先级(ModuloSuffixOrdinalizer.cs):先检查最后两位数区间规则,再查精确数值映射,然后查后两位后缀表,最后回退到末位数字后缀表与默认后缀。这正是11th、12th、13th不同于1st、2nd、3rd的底层原因(11-13的末位1、2、3被精确映射覆盖为th)。
显式指定文化的示例
// 指定文化,与当前线程文化无关 1.Ordinalize(new System.Globalization.CultureInfo("pt-BR"), Humanizer.GrammaticalGender.Feminine); // "1ª" "1".Ordinalize(new System.Globalization.CultureInfo("en-US")); // "1st"文化相关的数字格式化细节
Ordinalize在内部还会根据文化格式化数字本身(OrdinalizeExtensions.cs):对正数,若该文化的NumberFormat.NativeDigits是标准 ASCII 数字(UsesInvariantDigits为true),则用不变文化格式化,否则使用该文化自身的数字格式(例如某些语言的原生数字字符)。负数则额外比对文化的负号与NumberNegativePattern,以决定是否直接用不变文化输出。这些数字格式化配置按文化名缓存在ConcurrentDictionary<string, OrdinalNumberFormatting>中(OrdinalizeExtensions.cs),避免每次调用重复构建。
全系列重载一览
综合原 API 文档与 src/Humanizer/OrdinalizeExtensions.cs,Ordinalize的完整重载矩阵如下(均返回string):
| 接收者 | 参数组合 |
|---|---|
int number | ()/(WordForm)/(CultureInfo?)/(CultureInfo?, WordForm)/(GrammaticalGender)/(GrammaticalGender, WordForm)/(GrammaticalGender, CultureInfo?)/(GrammaticalGender, CultureInfo?, WordForm) |
string numberString | 同上 8 种组合 |
long number | 同上 8 种组合 |
其中WordForm用于区分缩写与完整词形。例如西班牙语(源码注释中的示例,OrdinalizeExtensions.cs):
"1".Ordinalize(WordForm.Abbreviation) -> 1.er // 如 "Vivo en el 1.er piso"(我住在一楼) "1".Ordinalize(WordForm.Normal) -> 1.º // 如 "Fui el 1º de mi promoción"(我是班里第一名) 1.Ordinalize(GrammaticalGender.Feminine, WordForm.Normal) -> 1.ª注意:原 API 文档生成的版本中string接收者仅收录了int/string两类;long重载在源码中同样完整存在(OrdinalizeExtensions.cs),并通过ILongOrdinalizer接口提供原生 64 位支持(详见下文)。
底层架构:IOrdinalizer 与 64 位支持
Ordinalize之所以能做到多语言、多形式输出,是因为它完全委托给可插拔的序数化器接口(src/Humanizer/Localisation/Ordinalizers/IOrdinalizer.cs):
public interface IOrdinalizer { string Convert(int number, string numberString); string Convert(int number, string numberString, WordForm wordForm); string Convert(int number, string numberString, GrammaticalGender gender); string Convert(int number, string numberString, GrammaticalGender gender, WordForm wordForm); } public interface ILongOrdinalizer : IOrdinalizer { string Convert(long number, string numberString); // ... 对应的 WordForm / gender 重载 }ILongOrdinalizer用于支持超出int范围的 64 位值。在long重载的ConvertOrdinalizer辅助方法中(OrdinalizeExtensions.cs),如果注册的序数化器实现了ILongOrdinalizer,则直接传入long;否则尝试把值安全收缩到int,若超出范围则抛出:
NotSupportedException: The registered ordinalizer '{type}' does not support 64-bit values.这意味着:在默认的英语环境下,long.MaxValue.Ordinalize()可以正常工作(DefaultOrdinalizer及其派生类实现了ILongOrdinalizer);但若你通过 Configurator 注册了一个仅实现IOrdinalizer的自定义序数化器,超大long值会抛出上述异常——这是扩展自定义序数化器时需要注意的边界。
另外,string重载在解析与归一化上做了两层处理(OrdinalizeExtensions.cs):
ParseOrdinalNumber:按指定文化解析(int.Parse(numberString, culture));NormalizeOrdinalNumberString:剔除字符串中的 Unicode格式类字符(UnicodeCategory.Format,如不可见的控制/格式符),保证传入IOrdinalizer的字符串干净一致。
测试验证与可靠性
仓库提供了详尽的测试用例来锁定Ordinalize行为。tests/Humanizer.Tests/OrdinalizeTests.cs 通过[Theory]+[InlineData]覆盖了大量输入-输出对,例如int系列(0→"0th"、1→"1st"、2→"2nd"、3→"3rd"、4→"4th"、11→"11th"、21→"21st"、101→"101st"等),以及string系列的同构断言。运行测试即可验证:
dotnet test tests/Humanizer.Tests/Humanizer.Tests.csproj --filter "FullyQualifiedName~Ordinalize"实用注意事项小结
- 只接受整数:
Ordinalize的语义限定于整数值(类注释明确说明,见 OrdinalizeExtensions.cs);小数需自行取整后再调用。 culture为null时:按源码实现(而非原文档措辞)实际使用CultureInfo.CurrentCulture(当前线程文化)作为回落值。- 默认文化行为:
int/long的无参数重载均委托给CultureInfo.CurrentCulture版本,因此输出随线程当前文化变化。 - 性别只对特定语言生效:
GrammaticalGender主要影响巴西葡萄牙语(1º/1ª)等区分性别的语言;对英语等无性别后缀的语言,性别参数通常被忽略(SuffixOrdinalizer按性别选择,而英语的ModuloSuffixOrdinalizer不感知性别)。 - 自定义序数化器:可通过
Configurator.Ordinalizers注册新的IOrdinalizer/ILongOrdinalizer实现来扩展;若注册项仅实现 32 位接口,注意 64 位大数会抛NotSupportedException。
借助这套 API,你可以在极少的代码量下输出符合多种语言习惯的序数文本——从英文的1st/2nd/3rd/4th,到巴西葡萄牙语的1º/1ª,再到西班牙语的1.er/1.º,无需手写任何规则分支。
- 开发工具
【免费下载链接】Humanizer
Humanizer meets all your .NET needs for manipulating and displaying strings, enums, dates, times, timespans, numbers and quantities
相关推荐
Humanizer 序数化扩展(OrdinalizeExtensions)完全指南:从 1st/2nd/3rd 到多语言序数输出
Humanizer 序数化扩展(OrdinalizeExtensions)完全指南:从 1st/2nd/3rd 到多语言序数输出 OrdinalizeExten
开发工具Humanizer 序数化扩展(OrdinalizeExtensions)完全指南:从 1st/2nd/3rd 到多语言、多性别的序数格式化
Humanizer 序数化扩展(OrdinalizeExtensions)完全指南:从 1st/2nd/3rd 到多语言、多性别的序数格式化 Ordinaliz
开发工具Humanizer OrdinalizeExtensions 序数化完全指南:从 `1st` 到多语言序数输出
Humanizer OrdinalizeExtensions 序数化完全指南:从 1st 到多语言序数输出 导读 本文以 Humanizer 项目中的 Ordi
开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考