C# 10 新功能 -- Lambda

C# 10 此次的更新關於 Lambda expression 也帶來了一些新的功能,包含了委派型別推斷、顯式宣告回傳型別與 Attributes 的掛載。

Infer delegate type

自從 C# 可以用 var 以後 ( 沒記錯應該是 C#  3 ),大部分的 C# 開發人員應該都用 var 用的很爽,但在委派型別的時候爽度就少了一點:

這迫使你需要這樣寫:

// 以前
var func = new Func<string, int>((s) => int.Parse(s));

雖然這也不是甚麼很大的困擾,不過 C# 10 改善了這一點還是讓人揪感心,以後我們就可以這麼寫了,爽度提高:

var func = (string s) => int.Parse(s);

編譯器會自動推斷且把 var 轉譯為 Func<string,int>,挺好的,少打好多字。但是這邊有一個小細節,其實我們以前寫 Lambda 很少會明確宣告參數的型別,但是在目前編譯器的設計還沒有做到這個地步,希望以後能夠做到,那就更爽了。所以截至目前為止,以下的寫法是行不通的 ( 不過我猜這功能應該有機會在更後面版本的 C# 實現 ):

// C# 10.0 還無法在這個寫法下推斷型別
var func1 = (s) => int.Parse(s);

方法群組 ( method group ) 也可以這麼用:

var read = Console.Read;

不過在方法群組的用法上是有限制的,這個限制是該方法群組不能有多載,例如以下的用法就會出問題:

因為編譯器沒法知道你要用的是哪一個多載,所以無法決定委派的回傳值或參數應該怎麼推斷。

當編譯器可以推斷委派執行個體型別後,同時共變性也就成立了,所以上方的敘述式在指派運算子的左方變數型別就能支持共變性,也就是說我們也可以這麼寫,雖然這情形可能很少見:

object func1 = (string s) => int.Parse(s);
Delegate func2 = (string s) => int.Parse(s);
MulticastDelegate func3 = (string s) => int.Parse(s);

但另一個用法可能就比較常見了 – 利用 Lambda 表達式產出 Expression / LambdaExpression 執行個體:

Expression exp1 = (string s) => int.Parse(s);
LambdaExpression exp2 = (string s) => int.Parse(s);
Declared return type

第二個新增的功能是在 Lambda 表達式可以明確宣告回傳型別,所以現在的 Lambda 表達式也可以這麼寫:

var choose1 = object (bool b) => b ? 1 : "two";
// 或
var choose2 = IComparable (bool b) => b ? 1 : "two";

這大概是在回傳的執行個體型別擁有相同介面或是同樣的基底類別的狀況下可以省去寫轉型的問題,以前遇到這種狀況大概會寫成這樣:

// 以前
var choose2 = new Func<bool, IComparable>((bool b) => b ? (IComparable)1 : "two");

老實說,以前的寫法不僅寫的彆扭,看了也彆扭。

Attributes

這個功能就是讓 Lambda 表達式能掛載屬性,不過由於 Lambda 表達式是透過委派的 Invoke 來呼叫,所以 Invoke 是不會特別去處理 Attributes,根據微軟文件的說法是這個功能主要是為了程式碼分析所新增的,詳情可以參考 Lambda expressions (C# reference) #Attributes

這些 Attributes 可以掛在表達式本身 ( 編譯後就成為掛在方法上的 Attributes ),也可以掛在回傳值或參數 :

// 掛在方法上
Func<string, int> parse = [MyAttribute(1)] (s) => int.Parse(s);
var choose = [MyAttribute(1)][YourAttribute(2)] object (bool b) => b ? 1 : "two";
// 掛在參數
var sum = ([MyAttribute(1)] int a, [MyAttribute(2), YourAttribute(3)] int b) => a + b;
// 掛在回傳值
var inc = [return: MyAttribute(1)] (int s) => s++;