枚举中文描述别再靠反射:用 Roslyn 源生成器在编译期把 switch 生成好
一、一个每天都在写、也每天都在出错的样板代码
.NET 项目里给枚举挂中文描述,标准写法是加 Description 特性,然后靠反射读出来:
public enum SparePartStatus |
三个问题在实际项目里反复出现:
- 每次调用都走一次特性查找,于是又加一个
ConcurrentDictionary缓存; - 缓存要预热,冷启动第一批请求仍然会打反射;
- Native AOT 下
GetCustomAttribute可能被裁剪掉,编译不报错,运行时静默返回null——排查起来最难的一类问题。
第三点是决定性的。 reflection 方案在 AOT 场景基本不可用,而源生成器方案天然兼容,因为它没有需要裁剪的反射依赖。
二、换成编译期生成,形态是什么样
Roslyn 提供 IIncrementalGenerator,可以在编译期间分析语法树,直接往项目里注入生成好的 C# 代码。思路是:扫描带特定特性的枚举,生成一个 switch 表达式映射。
// 生成结果(示意) |
收益是三层:
- 运行时开销归零。生成的是编译时常量折叠的 switch,JIT 出来的机器码基本就是几条比较加跳转,没有字典查找;
- AOT 天然可用;
- 漏项在编译期暴露。反射方案里”新加一个枚举值忘了写描述”只能靠线上返回
null才发现;生成器可以在检测到未覆盖成员时报编译警告——这条比性能收益值钱得多。
三、生成器骨架
[] |
工程上三条注意事项,都是踩过的位置:
- 用
IIncrementalGenerator而不是老的ISourceGenerator。 增量版本有缓存,输入不变就不重跑;在大型解决方案里这个差距是编译时间级别的。 - **生成文件加
.g.cs后缀并标记// <auto-generated />**,让 IDE 与代码统计跳过它。 - 不要让生成器去读运行时才能拿到的信息。 它只能看语法树和编译模型;需要动态内容的映射,不适合这一路。
四、语言版本这条线,别再猜
顺便厘清几个常被混着说的特性,免得写文档时出错:
- 源生成器自 Roslyn 4 起进入正式支持,Visual Studio 2022 时代可用;
- 正则表达式源生成器自 .NET 7 起可用(
[GeneratedRegex]配合 partial 方法); - partial 属性是 C# 13 引入的,主要目的就是支持源生成器;
- C# 14 / .NET 10 又把 partial 扩展到事件与构造函数,并引入 extension members、
field关键字等。
也就是说:让源生成器”往你的类型上添成员”这件事,语言层面一直在为它铺路。枚举描述只是最容易下手的一个例子。

五、洹拓视角:什么时候值得为它建一个工程
坦白说,为一个枚举描述专门写生成器是过度工程。判断标准我们只看两条:
值得做的情况: 项目里存在几十个这样的枚举(订单状态、审批状态、库存类型、备件类型……MES/ERP 类系统非常容易出现);或者已经有 Native AOT / 裁剪 / 移动端启动性能约束;或者你吃过”新增枚举值忘了配文案导致界面显示空白”的线上问题。
不值得做的情况: 三五个枚举、没有 AOT 需求、也不在性能敏感路径上。这时候一个手写 switch 加一条 code review 规则就够了——引入生成器等于引入一个新的构建环节和一个团队必须学习的概念,它的成本要算进决策里。
我们在给客户做架构评估时,会把”是否值得建源生成器工程”归类为规模化才划算的基础设施投入,和大仓的构建缓存是同一档判断。豫北这边不少软件公司做MES、WMS 定制,几十上百个状态枚举是常态,这一类改造的投入产出比通常是正的。
反射方案与源生成器方案的差异、AOT 裁剪行为及 C# 各版本 partial 相关特性均与公开文档和社区实现核对;选型门槛为洹拓科技自身判断。
新乡洹拓科技有限公司,软件开发 · AI模型研发 · 云服务与部署运维。
咨询热线:400-829-9957
地址:河南省新乡市辉县市金城百货四楼