本文介紹 C# 6 的三個新語法:nameof 表示式、字串插值、和 null 條件運算子。

nameof 表示式

C# 6 新增的 nameof 關鍵字可用來取得某變數的名稱。請看底下這個簡單範例,便能了解其作用:

    string firstName = "Joey";
    string varName = nameof(firstName);  // 編譯結果:varName = "firstName"

程式碼右邊的註解已經透露,nameof 是作用於編譯時期,而非執行時期。

那麼,它可以用在哪裡呢?

比如說,為了防錯,我們經常在函式中預先檢查參數值是否合法,並將不合法的參數透過拋異常(exception)的方式通知呼叫端(或者寫入 log 檔案以供將來診斷問題)。像這樣:

    void LoadProduct(string productId)
    {
        if (productId == null)
        {
            throw new ArgumentNullException("productId"));
        }
        ....
    }

如此一來,當程式出錯時,用戶端就能輕易知道是哪個參數不對。問題是,程式中的參數名稱是寫死的字串值("productId"),將來萬一要修改參數名稱,稍不注意就會漏改這些字串。於是,即使煞費周章,仍有人選擇在執行時期動態取得參數名稱,只為了避免在程式中寫一堆固定的字串。其實,參數名稱在編譯時期就已經決定了,何不由編譯器代勞,既省事又不犧牲執行效率?

現在,C# 6 提供了 nameof 表示式,正好可以解決這個問題。於是剛才的範例可以改寫成這樣:

    void LoadProduct(string productId)
    {
        if (productId == null)
        {
            throw new ArgumentNullException(nameof(productId));
        }
        ....
    }

其中的 nameof(productId) 表示式在經過編譯之後,結果就等於手寫的固定字串 "productId"。

字串插值

.NET Framework 提供的字串格式化方法 String.Format(...) 是一種對號入座的寫法,相當好用。現在,C# 6 提供了另一種格式化字串的寫法,名曰「字串插值」(string interpolation)。

字串插值的基本語法,是在以雙引號包住的字串前面加上一個 '$' 字元,而在字串內容的部分使用一對大括弧 {} 來包住一個變數名稱。如下所示:
$"{變數名稱:格式規範}"

其中的「格式規範」就跟 String.Format() 方法所使用的格式規範字元一樣。一個格式字串裡面可以有多對大括弧,用來帶入不同的變數值,而沒有被大括弧包住的部分則維持不變。 參考底下的範例:

    string firstName = "Michael";
    string lastName = "Tsai";
    int salary = 22000;

    string msg1 = String.Format("{0} {1} 的月薪是 {2:C0}", firstName, lastName, salary);
    string msg2 = $"{firstName} {lastName} 的月薪是 {salary :C0}";
    Console.WriteLine(msg1);
    Console.WriteLine(msg2);

結果兩次輸出的字串值都相同,如下所示:

Michael Tsai 的月薪是 22,000
Michael Tsai 的月薪是 22,000

跟既有的 String.Format() 方法比較,我覺得字串插值的寫法更容易解讀,更容易想像最終格式化的結果。原因在於,解讀 String.Format() 時,我們必須把右邊的參數依序帶入左邊的格式字串;如果參數很多,有時還會對不上,導致順序錯置。新的字串插值語法則是直接在格式字串裡面填入變數名稱,不僅直觀,而且不會有弄錯順序的問題。

字串插值跟 nameof 表示式一樣都是語法糖,是編譯時期的魔法。明白地說,編譯器會把字串插值的語法編譯成 String.Format() 方法呼叫。底下是更多字串插值的範例,右邊註解則是編譯後的結果。

    $"姓名 = {myName}"    // String.Format("姓名 = {0}", myName)
    $"小時 = {DateTime.Now:hh}"  // String.Format("小時 = {0:hh}", DateTime.Now)
    $"{{測試大括弧}}"      // String.Format("{{測試大括弧}}")
    $"{{秒 = {DateTime.Now :ss}}}" // String.Format("{{秒 = {0:mm}}}", DateTime.Now) 

需特別注意的是,兩個連續的大括弧代表欲輸出一個大括弧字元。故第三個例子的執行結果為 "{測試大括弧}"。

Null 條件運算子

保險起見,在需要存取某物件的屬性之前,我們通常會先檢查該物件是否為 null,以免程式執行時拋出異常(NullReferenceException)。一般常見的寫法如下:

static void NullPropagationDemo(string s)
{
    if (s != null && s.Length == 4) // 只有當 s 不為空時才存取它的 Length 屬性。
    {
        // Do something.
    }
}

C# 6 新增了 null 條件運算子語法,讓你用更簡短的語法達到同樣效果:先判斷物件是否為 null,不是 null 才會存取其成員。它的寫法是在變數後面跟著一個問號,然後是存取成員名稱的表示式。參考以下範例:

static void NullPropagationDemo(string s)
{
    if (s?.Length == 4) // 只有當 s 不為空時才存取它的 Length 屬性。
    {
        // Do something.
    }
}

更多範例:

int? length = productList?.Length; // 若 productList 是 null,則 length 也是 null。 
Customer obj = productList?[0];    // 若 productList 是 null,則 obj 也是 null。
int length = productList?.Length ?? 0;  // 若 productList 是 null,則 length 是 0。
string name = productList?[0].FullName; // 若 productList 是 null,則 name 是 null。

小結

本文介紹的三種新語法當中,我會比較常用的應該是 nameof 和字串插值吧。
Happy coding!

摘自《C# 本事》(alpha 版)