- 开发工具
【免费下载链接】Humanizer
Humanizer meets all your .NET needs for manipulating and displaying strings, enums, dates, times, timespans, numbers and quantities
Humanizer 是一套面向 .NET 的字符串、枚举、日期、时间与数字处理工具库,而CollectionHumanizeExtensions负责其中最关键的一环:把IEnumerable<T>集合"人化"成符合自然语言习惯的列表文本(如"1, 2 and 3")。本文以 CollectionHumanizeExtensions.md 收录的公开 API 为骨架,结合 CollectionHumanizeExtensions.cs 源码、CollectionHumanizeTests.cs 测试与各语言 Locale 数据,系统讲解 6 个重载的使用方式、自定义格式化函数、分隔符语义、多语言注册机制与底层实现原理。读完本文,你将能在自己的 .NET 项目中熟练使用Humanize生成带牛津逗号、符合文化习惯的集合列表,并能按需定制集合格式化器。
一、类概览与核心职责
CollectionHumanizeExtensions是一个静态扩展类,签名如下:
public static class CollectionHumanizeExtensions它继承自System.Object,本身不保存任何状态,所有方法均为对IEnumerable<T>的扩展方法,作用是把集合"Humanizes(人化)"为一个人类可读的列表字符串。其 XML 注释给出的定位是:
Humanizes an IEnumerable into a human readable list
从 CollectionHumanizeExtensions.cs 可以看到,类的全部 8 个公开扩展方法(文档版本列出 6 个核心重载)都只是薄薄的"外观层"——它们真正的工作全部委托给Configurator.CollectionFormatter(当前线程文化对应的集合格式化器),这种"扩展方法 + 配置器委托"的架构保证了:用户只需关心语义,分隔符、连词、逗号风格等文化差异由底层 formatter 自动处理。
二、六个核心重载:签名、参数与返回值
该 API 文档共收录 6 个重载,它们在是否传入displayFormatter、是否传入separator两个维度上排列组合,覆盖了绝大多数使用场景。统一约定如下:
- 泛型参数
T:集合元素类型; - 参数
collection:IEnumerable<T>,不能为null,否则抛出ArgumentNullException; - 参数
displayFormatter:逐元素格式化委托,两种形态Func<T, string>或Func<T, object>,均不能为null; - 参数
separator:元素间的分隔字符串; - 返回值:
string,即格式化后的列表文本。
| 重载 | 签名 | 行为 |
|---|---|---|
| ① | Humanize<T>(this IEnumerable<T> collection) | 对每个元素调用ToString(),使用当前文化的默认分隔符与连词(如英文", "+and) |
| ② | Humanize<T>(this IEnumerable<T> collection, string separator) | 对每个元素调用ToString(),使用调用方提供的分隔符 |
| ③ | Humanize<T>(this IEnumerable<T> collection, Func<T, object> displayFormatter) | 用委托格式化每个元素(返回值会被转成字符串),默认分隔符 |
| ④ | Humanize<T>(this IEnumerable<T> collection, Func<T, object> displayFormatter, string separator) | 委托格式化 + 自定义分隔符 |
| ⑤ | Humanize<T>(this IEnumerable<T> collection, Func<T, string> displayFormatter) | 用委托格式化每个元素(直接得到字符串),默认分隔符 |
| ⑥ | Humanize<T>(this IEnumerable<T> collection, Func<T, string> displayFormatter, string separator) | 委托格式化 + 自定义分隔符 |
1. 无参调用:默认文化分隔符
public static string Humanize<T>(this System.Collections.Generic.IEnumerable<T> collection);这是最常用的入口。源码中它直接委托给Configurator.CollectionFormatter.Humanize(collection)(见 CollectionHumanizeExtensions.cs),底层会走o => o?.ToString()+ 默认分隔符的路径(DefaultCollectionFormatter.cs)。
典型输出(英文文化):
new[] { 1, 2, 3 }.Humanize(); // "1, 2 and 3" new[] { "Alice", "Bob", "Charlie" }.Humanize(); // "Alice, Bob and Charlie" new[] { "single" }.Humanize(); // "single" new string[] { }.Humanize(); // ""注意空集合与单元素集合的边界行为:0 个元素返回空字符串"",1 个元素直接返回该元素本身(见 DefaultCollectionFormatter.cs)。
2. 指定分隔符
public static string Humanize<T>(this System.Collections.Generic.IEnumerable<T> collection, string separator);把默认连词替换为任意字符串。底层走Humanize(collection, o => o?.ToString(), separator)(DefaultCollectionFormatter.cs):
new[] { 1, 2, 3 }.Humanize(" | "); // "1 | 2 | 3" new[] { "Alice", "Bob" }.Humanize("; "); // "Alice; Bob"测试HumanizeUsesSeparatorWhenMoreThanOneItemIsInCollection验证了collection.Humanize("or")输出"A String or Another String"(见 CollectionHumanizeTests.cs)。
3/4. 对象形态的 displayFormatter(Func<T, object>)
public static string Humanize<T>(this System.Collections.Generic.IEnumerable<T> collection, System.Func<T, object> displayFormatter); public static string Humanize<T>(this System.Collections.Generic.IEnumerable<T> collection, System.Func<T, object> displayFormatter, string separator);委托返回任意object,底层通过objectFormatter(item)?.ToString()转字符串后拼接(DefaultCollectionFormatter.cs)。适用于"只投影不排版"的场景:
var numbers = new[] { 1, 2, 3 }; numbers.Humanize(n => n * 2); // "2, 4 and 6" numbers.Humanize(n => n * 2, " - "); // "2 - 4 - 6"对应测试HumanizeUsesObjectDisplayFormatter与HumanizeUsesObjectDisplayFormatterWhenSeparatorIsProvided(CollectionHumanizeTests.cs)分别验证了"1, 2, and 3"与"1, 2, or 3"的输出。
5/6. 字符串形态的 displayFormatter(Func<T, string>)
public static string Humanize<T>(this System.Collections.Generic.IEnumerable<T> collection, System.Func<T, string> displayFormatter); public static string Humanize<T>(this System.Collections.Generic.IEnumerable<T> collection, System.Func<T, string> displayFormatter, string separator);委托直接返回字符串,适合"先排版再拼接"的复杂投影,比如拼接对象多个字段:
var people = new[] { new Person { Name = "Alice", Age = 30 }, new Person { Name = "Bob", Age = 25 } }; people.Humanize(p => p.Name); // "Alice and Bob" people.Humanize(p => $"{p.Name} ({p.Age})"); // "Alice (30) and Bob (25)" people.Humanize(p => p.Name, " | "); // "Alice | Bob"对应测试HumanizeUsesStringDisplayFormatter与HumanizeUsesStringDisplayFormatterWhenSeparatorIsProvided验证了"SomeObject #1 - One, SomeObject #2 - Two, and SomeObject #3 - Three"等输出(CollectionHumanizeTests.cs)。
三、null 与空白元素的防御性语义
CollectionHumanizeExtensions的重载对委托参数统一做了ArgumentNullException.ThrowIfNull(displayFormatter)防御(CollectionHumanizeExtensions.cs)。除此之外,底层 formatter 还有两条对调用方友好的默认规则(见 ICollectionFormatter.cs 的 remarks):
- 忽略空/空白项:格式化结果为
null、空字符串或纯空白(IsNullOrWhiteSpace)的元素会被直接跳过,不参与拼接; - 自动 Trim:保留下来的元素会先
Trim()再去掉首尾空白。
这两条规则在测试中有明确证据:
// 空元素被移除 Assert.Equal("A and C", StringsWithEmptyItem.Humanize(DummyFormatter)); // ["A", " ", "C"] // 元素被 Trim Assert.Equal("A, B, and C", StringsWithWhitespace.Humanize(DummyFormatter)); // ["A", " B ", "C"]见 CollectionHumanizeTests.cs。实现位置在 DefaultCollectionFormatter.cs:AddDisplayString只接受非空白的Trim()结果。这意味着空集合、全空元素集合都会安全地返回"",集合中的null元素也不会抛异常(测试HumanizeHandlesNullItemsWithoutAnException等予以验证,见 CollectionHumanizeTests.cs)。
四、多文化支持:牛津逗号与本地化连词
集合"人化"最体现价值的地方在于文化差异。Humanize默认使用当前线程文化(Configurator.CollectionFormatter通过ResolveForCulture(null)解析,见 Configurator.cs);若需要显式指定文化,可调用Humanize(culture)形式的重载,它会走Configurator.CollectionFormatters.ResolveForCulture(culture)(CollectionHumanizeExtensions.cs),且culture为null时同样抛ArgumentNullException(测试HumanizeThrowsWhenCultureIsNull验证,见 CollectionHumanizeTests.cs)。
四种集合格式化器实现
文化注册表CollectionFormatterRegistry(CollectionFormatterRegistry.cs)为每个 Locale 解析出一个ICollectionFormatter实现。接口定义了 6 个与扩展方法一一对应的Humanize方法(ICollectionFormatter.cs),当前仓库共有 4 种实现:
| 实现类 | 语义 | 典型示例 |
|---|---|---|
DefaultCollectionFormatter | 用默认分隔符 + 连词拼接,前三项", "分隔、末项前用连词 | "A, B and C" |
OxfordStyleCollectionFormatter | 牛津逗号风格:3 项及以上在倒数第二项后加逗号 | "A, B, and C" |
CliticCollectionFormatter | 把末项连词当作"附缀(clitic)"与最后一项直接拼写 | 阿拉伯语"أ، ب و ج"(و直接缀在末项前) |
DelimitedCollectionFormatter | 纯分隔符拼接,所有可见项之间用同一分隔符 | 中文"A、B、C" |
以牛津逗号为例,OxfordStyleCollectionFormatter.cs 覆写了GetConjunctionFormatString:
protected override string GetConjunctionFormatString(int itemCount) => itemCount > 2 ? "{0}, {1} {2}" : "{0} {1} {2}";即仅当可显示项 ≥ 3 时,在连词前补一个逗号。测试HumanizeUsesOxfordComma验证了"A String, Another String, or A Third String"(CollectionHumanizeTests.cs)。
Locale 数据如何驱动格式化器
集合格式化器的选择并非硬编码在代码里,而是来自各语言的Locales/*.yml数据。每个 Locale 文件都含list:配置节,例如:
- 英文(en.yml)使用
engine: 'oxford',即英文默认就是牛津逗号风格; - 法语(fr.yml)使用
engine: 'conjunction'+value: 'et',连词为法语"和"; - 简体中文(zh-Hans.yml)使用
engine: 'delimited'+value: '、',即中文顿号分隔; - 阿拉伯语(ar.yml)使用
engine: 'clitic'+value: 'و',把连词作为附缀直接缀在末项前。
这些 YAML 配置在编译期由源生成器读取:HumanizerSourceGenerator中的 GenerationHelpers.cs 根据list.engine生成对应的 formatter 构造代码(oxford→OxfordStyleCollectionFormatter、conjunction→DefaultCollectionFormatter、clitic→CliticCollectionFormatter、delimited→DelimitedCollectionFormatter)。因此,新增一种集合风格只需要在 Locale YAML 中声明,无需改动运行时代码。
五、注册表结构与扩展机制
若想为某个文化自定义集合格式化行为,理解注册表结构是关键。CollectionFormatterRegistry继承自LocaliserRegistry<ICollectionFormatter>,其默认构造逻辑是:
public CollectionFormatterRegistry() : base(_ => new DefaultCollectionFormatter("&")) => CollectionFormatterRegistryRegistrations.Register(this);见 CollectionFormatterRegistry.cs。两点值得注意:
- 兜底默认值:未显式匹配任何文化的解析结果,会得到以
"&"为连词的DefaultCollectionFormatter; - 注册由源生成器填充:
CollectionFormatterRegistryRegistrations.Register(this)是由Humanizer.SourceGenerators在编译期生成的注册代码,把每个 Locale 的 formatter 实例写入注册表——这也是为什么 YAML 数据能驱动运行时的原因。
对外,Configurator.CollectionFormatters属性暴露了LocaliserRegistry<ICollectionFormatter>(Configurator.cs),你可以仿照 Humanizer 其它注册表(如FormatterRegistry、NumberToWordsConverterRegistry)的做法,通过注册表注入自定义ICollectionFormatter实现,从而覆盖任意文化的列表风格。
六、性能与实现细节(源码视角)
从源码结构看,四种实现都对"少元素路径"做了刻意优化:
DefaultCollectionFormatter对 0/1 个可见元素直接返回,不做字符串格式化;2 项与多项通过JoinLeadingItems拼接前导项、最后用GetConjunctionFormatString收尾(DefaultCollectionFormatter.cs);DelimitedCollectionFormatter用StringBuilder统一追加分隔符,并把首个可见项留在 builder 之外,使单元素路径零分配(DelimitedCollectionFormatter.cs);CliticCollectionFormatter类似地先持有首项、再把倒数第二项并入逗号头部,末项保持独立以便连词"附缀化"(CliticCollectionFormatter.cs)。
此外CreateDisplayItems会根据集合是否实现ICollection<T>/IReadOnlyCollection<T>预分配容量,避免不必要的扩容(DefaultCollectionFormatter.cs)。
七、实战速查:常见调用模式
| 需求 | 代码 | 输出(en 文化) |
|---|---|---|
| 简单列表 | new[] { "A", "B", "C" }.Humanize() | A, B, and C |
| 自定义分隔符 | new[] { "A", "B" }.Humanize(" or ") | A or B |
| 投影数值 | new[] { 1, 2, 3 }.Humanize(n => n * 2) | 2, 4, and 6 |
| 投影并排版 | people.Humanize(p => $"{p.Name} ({p.Age})") | Alice (30) and Bob (25) |
| 指定文化 | new[] { "A", "B", "C" }.Humanize(new CultureInfo("en-GB")) | A, B and C |
| 空集合 | Array.Empty<int>().Humanize() | "" |
测试HumanizeUsesSpecifiedCulture验证了en-GB下三元素输出"A, B and C"(CollectionHumanizeTests.cs),而HumanizeUsesSpecifiedCultureForEverySupportedLocale则对所有支持语言逐语言断言两元素/三元素的输出(CollectionHumanizeTests.cs),可作为你验证多语言行为的第一手参考。
总结
CollectionHumanizeExtensions以 6 个轻量重载覆盖了集合列表"人化"的全部常用场景:默认文化、自定义分隔符、字符串/对象两种投影委托,并且通过ICollectionFormatter+ Locale YAML + 源生成器的组合,让牛津逗号、法语et、阿拉伯语附缀و、中文顿号、等文化差异在运行时自动生效。理解 CollectionHumanizeExtensions.cs 的委托结构、ICollectionFormatter.cs 的接口契约与 CollectionHumanizeTests.cs 的边界用例,即可在项目中放心使用,也能按需扩展自己的列表风格。
- 开发工具
【免费下载链接】Humanizer
Humanizer meets all your .NET needs for manipulating and displaying strings, enums, dates, times, timespans, numbers and quantities
相关推荐
Humanizer CollectionHumanizeExtensions 集合人性化指南:让 IEnumerable 输出变成自然语言列表
Humanizer CollectionHumanizeExtensions 集合人性化指南:让 IEnumerable 输出变成自然语言列表 导读 Colle
开发工具Humanizer 完全指南:用 .NET 库把字符串、枚举、日期与数量变成人类可读文本
Humanizer 完全指南:用 .NET 库把字符串、枚举、日期与数量变成人类可读文本 Humanizer 是一个面向 .NET 的文本人性化库,专门把字符串
开发工具Humanizer DateHumanizeExtensions 深度解析:把 DateTime / DateOnly / TimeOnly 变成"3 days ago"式自然语句
Humanizer DateHumanizeExtensions 深度解析:把 DateTime / DateOnly / TimeOnly 变成"3 days
开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考