C# 10和.NET 6中的常量插值在10分鐘內完成
隨著每一個新版本的推出,C# 在功能和優雅方面持續演進,為開發者提供更簡潔與高效的編碼方式。 C# 10 中新增了一個功能是常數插值字串,這讓開發者可以在常數內使用字串插值。
在這個功能出現之前,如果您嘗試使用插值來創建常數字串(例如,"${companyName} Products"),編譯器會拋出錯誤。 您只能將純字串文字賦值給常數值,這在構建常數訊息或屬性字串時常常導致重複或笨重的程式碼。
現在,從 C# 10 和 .NET 6 開始,您可以直接在編譯時使用插值字串來組合常數。這一改進不僅使您的程式碼更簡潔,而且保持高效,因為所有值在程式運行前即已被評估。
為了更清楚地了解這是如何工作的,Tim Corey 在他的视频 "Constant Interpolation in C# 10 and .NET 6 in 10 Minutes or Less" 中以一步步的方法解析了這一概念。使用他一貫的簡潔實用教學風格,Tim 解釋了這個新功能的作用、它與傳統字串連接的區別,以及它在何處特別有用——特別是在屬性中。
那麼讓我們一起來看看 Tim 的解說,了解C#中的常數插值字串到底是如何運作的,以及它為什麼是開發者的一個有用新增功能。
介紹
在這段視頻中,Tim 解釋了如何使用熟悉的美元符號($)語法的插值字串現在可以用於常數表達式,這在之前是無法做到的。 這一小改變使常數字串的構建更簡單,更整潔,更易於維護。
探索 .NET 6 中的常數
Tim 打開了一個 .NET 6 的控制台應用,並移除樣板程式碼以專注於常數定義。
他從最基本的常數字串宣告開始:
const string companyName = "Acme";
const string companyName = "Acme";
這是一個簡單的字串文字賦值給常數字串。 這樣的常數在編譯時被評估,這意味著它們的值是固定的,並嵌入到已編譯的程式中。
但 Tim 迅速轉向核心問題:如果我們想將常數字串合併在一起,或者直接在其他字串中使用插值來嵌入值呢?
在 C# 9 及更早的版本中,您無法這樣做:
const string productName = $"{companyName} Anvils"; // Not allowed before C# 10
const string productName = $"{companyName} Anvils"; // Not allowed before C# 10
這行會拋出編譯時錯誤,因為常數表達式不支持字串插值。
C# 10 中的常數插值字串
如 Tim 所示,在 C# 10 中,編譯器現在支持常數插值字串——只要所有插值表達式本身都是常數。
所以,以下範例現在運行得很好:
const string productName = $"{companyName} Anvils";
const string productName = $"{companyName} Anvils";
這是一個常數插值字串,意思是編譯器在編譯時而不是運行時評估插值字串。程式運行時不會發生額外的字串連接或字串格式化——編譯器生成一個單一的常數字串文字,如 "Acme Anvils"。
Tim 解釋說,如果我們將 companyName 的值從"Acme"改成"ABC",編譯器會自動為productName生成"ABC Anvils"。 這是編譯時的字串構建,而不是運行時插值。
這一改進使合併常數字串變得容易得多,而不需要使用+連接或手動重複值。
嵌套的常數插值
Tim 更進一步,給出了另一個常數定義:
const string productDescription = $"{productName} are the best way to crush unsuspecting roadrunners.";
const string productDescription = $"{productName} are the best way to crush unsuspecting roadrunners.";
這是一個嵌套插值的例子,其中一個常數插值字串(productName)用於另一個中。
編譯器將這些全部視為常數表達式,並在編譯時生成一個單一的,不可變的字串表示。
當 Tim 運行程式時,輸出為:
Acme Anvils are the best way to crush unsuspecting roadrunners.
Acme Anvils are the best way to crush unsuspecting roadrunners.
這證實了常數插值即使在多個常數之間也能無縫工作。
為什麼常數很重要
此時,Tim 停下來解釋為什麼常數——以及現在的常數插值字串——是有益的。
他指出,常數的記憶體效率非常高,因為它們的值是直接儲存在編譯程式中的,而不是記憶體中的單獨實例。
相比之下,當開發者以前需要類似的東西時,往往會使用 readonly 字段:
readonly string companyName = "Acme";
readonly string companyName = "Acme";
但 Tim 指出,readonly 字段和 const 不一樣——它們是在運行時評估,這消耗更多的記憶體,並防止編譯時優化。
有了常數插值字串,我們現在可以寫出既表達力又可重用的格式化字串,這仍保持編譯時常數,提升了清晰度和性能。
實際範例——在屬性中使用常數
接著,Tim 介紹了一個真實場景,這個新功能在其中大放異彩——屬性。
他在 Main() 中定義了一個簡單的本地方法:
void SayHi() { }
void SayHi() { }
然後他嘗試利用 [Obsolete] 屬性,帶有引用變數的字串訊息:
string myCompany = "Tim's Company";
[Obsolete($"This is no longer used for {myCompany}")]
string myCompany = "Tim's Company";
[Obsolete($"This is no longer used for {myCompany}")]
這失敗了,因為屬性只能接受常數表達式作為參數。 編譯器生成錯誤,因為 myCompany 是一個變數,而不是常數。
Tim 解釋,屬性的訊息需要是編譯時常數——它不能依賴於運行時的值或實例變數。
然而,由於 C# 10 的常數插值字串,我們現在可以安全地做到這一點:
const string productName = $"{companyName} Anvils";
[Obsolete($"This is no longer used for {productName}")]
const string productName = $"{companyName} Anvils";
[Obsolete($"This is no longer used for {productName}")]
在這裡,編譯器識別出 companyName 和 productName 都是常數,因此整個插值字串是常數表達式。
編譯器在編譯時生成格式化字串,讓它在屬性中有效。
這個例子完美地展示了為什麼常數插值不僅僅是語法糖——它開啟了使用格式化編譯時字串直接在屬性或中繼資料中的新場景。
幕後——編譯器是如何處理的
Tim 在視頻中沒有深入討論編譯器內部細節,但這個概念與 C# 10 中的插值字串處理器有密切的關係。
一般來說,當編譯器遇到插值字串時,它會創建類似於格式字串操作的程式碼,幕後生成類似 AppendLiteral() 和 AppendFormatted() 的呼叫。
但在處理常數插值字串時,編譯器在編譯時評估一切——在生成的IL程式碼中沒有插值字串處理器或方法呼叫被發出。
這意味著結果值的行為就像任何字串文字一樣,但您仍然可以使用其他常數中的嵌入表達式來組合它。
這是表達力與效率之間的優雅平衡——編譯器靜態地處理字串建構,確保零運行時成本。
何時使用常數插值字串
Tim 承認並不是每個人每天都會使用這個功能。 很少定義常數或撰寫屬性的開發者可能不會立即獲益。
然而,對於那些創建許多編譯時定義、常數訊息或屬性中繼資料的人來說,這個功能簡化了程式碼並防止了字串連接混亂。
這也更安全——因為常數是不可變的並被編譯器檢查,您消除了從動態連接字串或管理不善的變數中出現的錯誤。
這使您的程式碼更健壯、可讀和更容易維護。
結論
當 Tim 結束他的視頻時,他邀請開發者反思他們是否會在專案中使用常數插值字串。 有些人可能會發現它們對於更清晰的編譯時格式化字串來說是必不可少的,而其他人可能會認為它們是一種小便利。
無論如何,Tim 的演示清楚地展示了如何有效實施及應用這一功能。
總結:
-
常數插值字串允許在常數內使用插值表達式。
-
它們在編譯時被評估,生成高效的字串文字。
-
它們用更簡潔的語法取代了重複的字串連接。
- 它們特別有用於屬性、基於常數的配置和中繼資料信息。
通過將可讀性與編譯時安全性結合,C# 10 的常數插值是邁向表達力與效率的C#程式設計的又一步——正如 Tim Corey 的例子清楚地展示的那樣。
