C# 14 带着 .NET 10 一同发布了,带来了一系列诸如扩展成员、field
关键字、空条件赋值等不错的"生活质量"改进。但说实话,对于我们这些老鸟来说,社区的期待往往是更高的。每年我们都盼着语言能来点"核弹级"更新,结果发现,真正让我们心痒痒的那些大特性,却在官方的"工作集"和"积压项"里徘徊,成了 C# 14 的"幽灵"。
不过,这种克制并非停滞。恰恰相反,这正是一门成熟语言深思熟虑的体现。它告诉我们,C# 团队的核心理念是:宁愿慢一点,也要保证每一步都踩得稳、踩得准。今天,我们就来聊聊这些被推迟的"幽灵",看看它们背后到底有哪些惊心动魄的故事,以及它们如何揭示 C# 未来的走向。

设计的艺术:C# 新特性是如何诞生的?
想搞明白为什么有些特性会"跳票",就得先了解一个 C# 特性从点子到落地的全过程。这个过程基本上是全透明的,主要围绕着 dotnet/csharplang 这个 GitHub 仓库 进行。
简单来说,一个想法从 Issue 开始,如果够有分量,就会有 C# 团队成员来当"拥护者"(Champion),然后进入语言设计会议(LDM)被反复捶打。LDM 可不是简单的投票,那是一群顶尖大佬进行深度设计和激烈辩论的"创意工场"。他们的会议纪要都是公开的,是理解特性背后"为什么"的绝佳一手资料。
而 csharplang 仓库里的里程碑(Milestones)则清晰地表明了特性的状态:
- Working Set:当前正在被 LDM 积极设计的特性,是下一个版本的"准候选人"。
- Backlog:有价值,但暂时没空搞,是未来版本的"潜力股"。
- Any Time:社区可以来贡献,但核心团队优先级不高。
- Likely Never:被 LDM 明确拒绝的提案。
这种开放又高度策划的流程,确保了 C# 在拥抱创新的同时,不会偏离其统一的设计愿景。
可辨识联合(Discriminated Unions):一场未竟的史诗
在所有被推迟的特性里,可辨识联合(Discriminated Unions, DUs)绝对是社区里呼声最高、设计最复杂、故事也最曲折的一个。它在 csharplang 仓库里是被点赞最多的 Issue 之一,其漫长的演进史,简直就是 C# 设计哲学的一面镜子。
为什么我们如此渴望 DU?
一句话概括 DU 的核心价值:在编译时,让非法的状态变得不可表示。这是函数式编程的基石,也是构建健壮系统的终极利器。
举个烂熟于心的例子:表示一个定时任务触发器。它可能有几种状态:从不、每天午夜、每日特定时间、或按周期。用传统的 class 或 struct,你可能会写出这样的代码:
csharp
// 传统方式,充满了挖坑的可能性
public struct JobTrigger
{
public bool IsNever { get; set; }
public bool IsEveryMidnight { get; set; }
public TimeOnly? DailyTime { get; set; }
public TimeSpan? Period { get; set; }
// ... 各种布尔值和可空类型
}
这种结构的问题简直是灾难性的:我可以轻易创建一个 new JobTrigger { IsNever = true, Period = TimeSpan.FromHours(1) }
这种逻辑上精神分裂的对象。你只能在运行时用一堆 if-else
去捕获和抛异常。
而一个理想的 DU 实现,则能在类型系统层面直接干掉这种可能:
csharp
// 理想中的 DU 语法(示意)
public union JobTrigger
{
case Never;
case EveryMidnight;
case Daily(TimeOnly time);
case Periodic(TimeSpan interval);
}
在这种设计下,一个 JobTrigger
实例必须 是这四种情况之一,且只能是其中之一。更牛的是,当你用 switch
表达式处理它时,编译器会进行穷尽性检查 。这意味着,如果未来你给 JobTrigger
增加了第五种情况,所有没处理新情况的 switch
都会直接编译失败,而不是等到运行时给你一个惊喜。
说到这里,我总会感到一阵惋惜。我们都知道,TypeScript 的编译器是用 TypeScript 写的,而 TypeScript 之父 Anders Hejlsberg 也是 C# 的缔造者。后来在新版本的 TypeScript 编译器重写时,Anders 大神选择了 Go,而不是自己的亲儿子 C#。坊间传闻,一个重要的原因可能就是当时 C# 缺乏原生的可辨识联合能力。如果 C# 早点拥有这个特性,以其卓越的类型系统和性能,或许就能成为重写 TypeScript 编译器的不二之选。唉,这或许是 .NET 生态永远的意难平了。
设计的迷宫:从语法到版本地狱
DU 虽好,但想把它完美地塞进 C# 这个庞大且极其注重向后兼容的语言里,简直是地狱级难度。
- 语法之战 :用
union
和case
关键字?还是用|
符号?每种方案都可能与现有代码冲突,引入新关键字更是要慎之又慎。 - 穷尽性检查的挑战 :这才是真正的"大魔王"。想象一下,一个流行的 NuGet 包定义了一个公共 DU 类型
Result
,包含Success
和Failure
。你的代码完美处理了这两种情况。然后,包更新了,加了个Cancelled
状态。你只更新了 DLL 而没重新编译,程序在运行时遇到Cancelled
就直接崩溃了。这直接破坏了 .NET 生态系统"二进制兼容"的基石承诺!F# 选择建议不在公共 API 暴露 DU,但这对于 C# 来说显然不是个好答案。 - 运行时与性能:底层怎么实现?是编译时检查、运行时"擦除"类型信息(类似 Java 泛型擦除,性能和互操作性堪忧),还是为每个联合生成一个真实的、带有元数据的"具体化"类型(对 CLR 改动巨大)?每一步都是艰难的权衡。
面对如此巨大的复杂性,LDM 最终做出了一个关键决策:放弃"大爆炸"式发布,转而采用增量式方法 。他们决定,当前阶段先集中精力搞定"类联合"(class unions),也就是基于现有类继承体系的、范围更小的 DU 实现。
这正是 C# 14 中没有 DU 的直接原因。LDM 选择了一条更务实的路径:先从最熟悉的类继承入手,发布一个 v1 版本。这很 C#,很务实。它采纳了函数式编程的理念,但通过我们面向对象开发者最熟悉的机制来实现它。
拦截器(Interceptors):在炼狱中挣扎的强大工具
如果说 DU 的故事是"慢工出细活",那拦截器的故事就是一场关于语言哲学和"代码魔法"的激烈辩论。最终,这个特性被打上了"实验性预览"的标签,未来充满了不确定性。
拦截器的诞生,源于一个非常具体的需求:为 .NET 的 AOT(预先编译)场景提供高性能方案。像 ASP.NET Core Minimal APIs 大量依赖运行时反射,这和 AOT 的静态分析天生就是死对头。
拦截器允许源码生成器在编译时"拦截"一个方法调用,并把它替换成另一段静态生成的、不含反射的高效代码。比如,对 app.MapGet("/", ...)
的调用,可以被重写为直接调用一个预生成好的处理程序。开发者体验不变,但编译产物却变得 AOT 友好了。
这看似完美的方案,却在 LDM 内部引发了深刻的哲学分歧:
- 务实的工具论者:认为这玩意儿就是个编译器优化工具,开发者不需要知道它的存在,只要代码能跑得快、调试体验好就行。
- 通用的语言特性论者 :对可能导致的"远距离幽灵行为 "(spooky action at a distance)表示严重担忧。一行
controller.DoSomething()
的代码,实际上执行的却是另一段逻辑,这简直是代码可读性的噩梦,堪称"不受限制的 comefrom 语句"。他们坚持,必须在调用点有个明确的语法标记(比如controller.DoSomething#()
),告诉开发者"这里有魔法"!
面对这种分歧和发布时间的压力,LDM 做出了一个"所罗门的审判":拦截器随 .NET 8 发布,但身份是明确的、不受支持的实验性特性。
这个决定,一方面解了 ASP.NET 团队的燃眉之急,另一方面也为语言的长期健康留下了思考时间。LDM 成立了一个新工作组,去重新审视这个需求,看看有没有侵入性更小的方式来解决。这充分体现了 LDM 作为语言"守护者"的决心,即使面对平台内部"第一方客户"的强大需求,也绝不牺牲语言长期的清晰性和一致性。
来自积压项的低语
除了上面两个"大部头",C# 的"积压项"里还潜藏着很多有趣的想法。
- 类型类(Type Classes):被标记为"需要长期投入",这是一种允许你为现有类型(即使是第三方库里的)扩展"接口"实现的能力,比扩展方法更强大。但它需要对 .NET 泛型系统和运行时进行伤筋动骨的改造,复杂性堪比当年引入泛型本身,所以只能是个遥远的愿景。
- 封闭枚举(Closed Enums):一个看起来很美好的小特性,阻止将任意整数强转为枚举,保证枚举值的安全。它之所以没被推进,很可能是被更宏大的 DU 提案"遮蔽"了光芒。LDM 可能认为,DU 已经能解决其核心问题,没必要再单独搞一个"半成品"。
C# 的"积压项"并非创意的坟场,而是一个战略孵化器。它表明 C# 团队拥有一个跨越数年的前瞻性视野,他们是在进行一种高度战略化的、对语言设计进行长期组合投资的管理。
未来展望:一个更深思熟虑的 C#
剖析完这些"幽灵"特性,C# 的演进原则也清晰地浮现出来:
- 清晰性至上:对任何可能引入"魔法"、模糊代码控制流的特性都保持高度警惕。
- 增量优于革命:即使是革命性的概念,也倾向于小步快跑、向后兼容的演进。
- 生态系统为王:对二进制兼容性和 NuGet 生态的敬畏,是阻止激进特性的强大"制动器"。
- 兼顾性能:对性能的追求,尤其是 AOT 场景,是创新的重要驱动力。
那么,我们可以大胆预测:
- C# 15:很可能会迎来"类联合"的第一个版本,这将是 C# 拥抱函数式编程的坚实一步。关于拦截器的故事也将有新进展。
- C# 16+:更复杂的 DU 形式和类型类等,依然在地平线的远方,将继续遵循其深思熟虑的节奏。
一门语言的价值,不仅在于它包含了什么,更在于它明智地选择了不包含什么。C# 14 的这些"幽灵",并非过去的遗憾,而是照亮未来的路标。它们预示着一个更加健壮、更具表达力,也更加深思熟虑的 C# 正在向我们走来。
感谢阅读到这里,如果感觉本文对您有帮助,请不吝评论 和点赞 ,这也是我持续创作的动力!
也欢迎加入我的 .NET骚操作 QQ群:495782587,一起交流.NET 和 AI 的各种有趣玩法!