那些被推遲的 C# 14 特性及其背后的故事
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,你可能會寫出這樣的代碼:
// 傳統方式,充滿了挖坑的可能性
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 實現,則能在類型系統層面直接干掉這種可能:
// 理想中的 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 的各種有趣玩法!

浙公網安備 33010602011771號