微軟從C#1.0的委托,到C#2.0的匿名方法,再到C#3.0的Lambda表達式,一步步升級,帶給我們的是代碼上的優美,簡潔,可讀性強。Lambda 表達式廣泛用於編寫LINQ查詢表達式等方面。本文全面的介紹了Lambda 表達式的概念、由來及使用方法。 ...
引言
在實際的項目中遇到一個問題,我們經常在網上搜索複製粘貼,其中有些代碼看著非常的簡潔,比如Lambda表達式,但是一直沒有去深入瞭解它的由來,以及具體的使用方法,所以在使用的時候比較模糊,其次,編程涉及面比較廣,我們不可能每個方面都去精通瞭解,但經常運到的一些東西,必須瞭解其具體使用方法及使用場景,才能書寫出優美、簡潔、可讀性強的代碼。筆者通過搜索、整理資料及測試代碼,詳細的介紹Lambda 表達式的用法。
Lambda 表達式概念
“Lambda 表達式”(lambda expression)是一個匿名函數,可以表示為委托的代碼,或者表示為表達式樹的代碼,它所表示的表達式樹可以編譯為委托。 Lambda 表達式的特定委托類型取決於其參數和返回值。不返回值的 Lambda 表達式對應於 Action
委托,具體取決於其參數數量。 返回值的 Lambda 表達式對應於 Func
委托,具體取決於其參數數量。
Lambda 表達式廣泛用於:
-
將要執行的代碼傳遞給非同步方法,例如 Task.Run(Action)。
-
編寫 LINQ 查詢表達式。
-
創建表達式樹。
C# 中委托的演變
在 C# 1.0 中,通過使用在代碼中其他位置定義的方法顯式初始化委托來創建委托的實例。 C# 2.0 引入了匿名方法的概念,作為一種編寫可在委托調用中執行的未命名內聯語句塊的方式。 C# 3.0 引入了 Lambda 表達式,這種表達式與匿名方法的概念類似,但更具表現力並且更簡練。 這兩個功能統稱為匿名函數。 通常,面向 .NET Framework 3.5 及更高版本的應用程式應使用 lambda 表達式。
C#1.0中委托的實現,代碼如下:
delegate int CalculateHandler(int x, int y); private int Sum(int x, int y) { return x + y; } public void Test() { CalculateHandler sumHandler =new CalculateHandler(Sum); MessageBox.Show(sumHandler(1, 2).ToString());//輸入結果3
}
C#2.0中匿名方法的實現,代碼如下:
delegate int CalculateHandler(int x, int y); public void Test() { CalculateHandler sumHandler = delegate(int x, int y) { return x + y; }; MessageBox.Show(sumHandler(1, 2).ToString());//輸入結果3 }
C#3.0中Lambda 表達式的實現,代碼如下:
delegate int CalculateHandler(int x, int y); public void Test() { CalculateHandler sumHandler = (x, y) => x + y; MessageBox.Show(sumHandler(1, 2).ToString());//輸入結果3 }
由此可以看出微軟的一步步升級,帶給我們的是編程上的優美,簡潔,可讀性強,因此作為程式員我們要一直處於學習的路上。
Lambda 表達式使用
C#的Lambda 表達式都使用 Lambda 運算符 =>,該運算符讀為“goes to”, 若要創建 Lambda 表達式,需要在 lambda 運算符左側指定輸入參數(如果有),然後在另一側輸入表達式或語句塊。 例如,單行 Lambda 表達式 x => x * x
指定名為 x
的參數並返回 x
的平方值。
在介紹Lambda 表達式使用之前我們先瞭解.Net為我們定義好的Action<T>和Func<T>兩個泛型委托。
Action<T>泛型委托
Action<T>委托表示引用一個返回類型為Void的方法。這個委托存在不同的變體,可以傳遞之多16個不同的參數類型。同時,沒有泛型參數的Action類可以調用沒有參數的方法。例如,Action<in T>表示有一個輸入參數的方法,Action<in T1,in T2>表示有兩個輸入參數的方法。
Func<T>泛型委托
Func<T>可以以類似的方法使用。不過Func<T>允許調用帶返回參數的方法。Func<T>也有不同的變體,之多可以傳遞16個參數和一個返回類型。例如:Func<out TResult>委托類型可以無參的帶返回類型的方法,Func<in T1,inT2,out Tresult>表示帶兩個參數和一個返回類型的方法。
Func<T>可以表示帶輸出的方法,T可以有多個,且只有最後一個表示輸出即最後一個是返回類型。Func<in T1,inT2,out Tresult>中的字元in、out在實際代碼中是不會出現的。
表達式 Lambda
表達式位於 =>
運算符右側的 Lambda 表達式稱為“表達式 lambda”。具體形式:(input-parameters) => expression,表達式 lambda 會返回表達式的結果。
具體事例,代碼如下:
public Action SuccessPrompt =() => MessageBox.Show("執行成功!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); //沒有參數,括弧不能省略 public Func<int, int, bool> Compare = (x, y) => x > y;//判斷x是否大於y
語句 Lambda
語句 Lambda 與表達式 lambda 表達式類似,只是語句括在大括弧中,具體形式:(input-parameters) => { statement; }。語句 lambda 的主體可以包含任意數量的語句;但是,實際上通常不會多於兩個或三個。
具體事例代碼如下:
public Action<string> Prompt = prompt => { MessageBox.Show(prompt, "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); };//僅當 Lambda 只有一個輸入參數時,括弧才是可選的;否則括弧是必需的
非同步 Lambda
通過使用 async 和 await 關鍵字,你可以輕鬆創建包含非同步處理的 lambda 表達式和語句。其中async
和 await
關鍵字是在 C# 5 中引入的。
await關鍵字
await
運算符應用於非同步方法中的任務,在方法的執行中插入掛起點,直到所等待的任務完成。 任務表示正在進行的工作。
await
僅可用於由 async 關鍵字修改的非同步方法中。 使用 async
修飾符定義並且通常包含一個或多個 await
表達式的這類方法稱為非同步方法。
async修飾符
使用 async
修飾符可將方法、lambda 表達式或匿名方法指定為非同步。 如果對方法或表達式使用此修飾符,則其稱為非同步方法。
使用非同步 lambda 添加事件處理程式。 若要添加此處理程式,請在 lambda 參數列表前添加 async
修飾符,代碼如下:
public partial class Form1 : Form { public Form1() { InitializeComponent(); button1.Click += async (sender, e) => { await ExampleMethodAsync(); textBox1.Text += "\r\nControl returned to Click event handler.\n"; }; } private async Task ExampleMethodAsync() { // The following line simulates a task-returning asynchronous process. await Task.Delay(1000);//Task.Delay方法只會延緩非同步方法中後續部分執行時間,當程式執行到await表達時,一方面會立即返回調用方法,執行調用方法中的剩餘部分,這一部分程式的執行不會延長。另一方面根據Delay()方法中的參數,延時對非同步方法中後續部分的執行。 } }
Lambda 表達式和元組
自 C# 7.0(對應 .NET Framework4.7和Visual Studio 2017 )起,C# 語言提供對元組的內置支持。 可以提供一個元組作為 Lambda 表達式的參數,同時 Lambda 表達式也可以返回元組。 在某些情況下,C# 編譯器使用類型推理來確定元組組件的類型。可通過用括弧括住用逗號分隔的組件列表來定義元組,通常,元組欄位命名為 Item1
、Item2
等等。但是,可以使用命名組件定義元組。
事例代碼如下:
public void Test1() { Func<(int, int, int), (int, int, int)> doubleItem = ns => (2 * ns.Item1, 2 * ns.Item2, 2 * ns.Item3); var itemList = (1, 2, 3); var resultDItemList = (itemList);//結果為[1, 2, 9] } public void Test2() { Func<(int x, int y, int z), (int, int, int)> doubleItem = ns => (2 * ns.x, 2 * ns.y, 2 * ns.z); var itemList = (1, 2, 3); var resultDItemList = (itemList);//結果為[1, 2, 9] }
含標準查詢運算符的 Lambda
在其他實現中,LINQ to Objects 有一個輸入參數,其類型是泛型委托 Func<TResult> 系列中的一種。 這些委托使用類型參數來定義輸入參數的數量和類型,以及委托的返回類型。 Func
委托對於封裝用戶定義的表達式非常有用,這些表達式將應用於一組源數據中的每個元素。
標準查詢運算符是組成 LINQ 模式的方法。 這些方法中的大多數都作用於序列;其中序列指其類型實現 IEnumerable<T> 介面或 IQueryable<T> 介面的對象。 標準查詢運算符提供包括篩選、投影、聚合、排序等在內的查詢功能。
共有兩組 LINQ 標準查詢運算符,一組作用於類型 IEnumerable<T> 的對象,另一組作用於類型 IQueryable<T> 的對象。 構成每個集合的方法分別是 Enumerable 和 Queryable 類的靜態成員。 這些方法被定義為作為方法運行目標的類型的擴展方法。 這意味著可以使用靜態方法語法或實例方法語法來調用它們。
具體事例代碼如下:
public class Sutdent { public string Id { get; set; } public string Name { get; set; } public int Age { get; set; } } static void Main(string[] args) { List<Sutdent> studentList = new List<Sutdent>(); studentList.Add(new Sutdent {Id = "001", Name = "張三", Age = 18}); studentList.Add(new Sutdent {Id = "002", Name = "李四", Age = 19}); studentList.Add(new Sutdent {Id = "003", Name = "王五", Age = 16}); studentList.Add(new Sutdent {Id = "004", Name = "趙六", Age = 17}); List<Sutdent> list1 = studentList.FindAll(st => st.Age > 17);//選擇年齡大於17的所有學生 List<Sutdent> list2 = studentList.Where(st => st.Age > 17).ToList();//選擇年齡大於17的所有學生 studentList.Sort((st1,st2)=>st2.Age-st1.Age);//按Age降序排列 List<string> list3 = studentList.Select(st => st.Name).ToList();//選擇列表中的所有名字 }
Lambda 表達式中的類型推理
編寫 Lambda 時,通常不必為輸入參數指定類型,因為編譯器可以根據 Lambda 主體、參數類型以及 C# 語言規範中描述的其他因素來推斷類型。
lambda 類型推理的一般規則如下:
-
Lambda 包含的參數數量必須與委托類型包含的參數數量相同。
-
Lambda 中的每個輸入參數必須都能夠隱式轉換為其對應的委托參數。
-
Lambda 的返回值(如果有)必須能夠隱式轉換為委托的返回類型。
總結
通過上邊的講解,我們可以看出Lambda表達式的用法非常的簡單,特別在標準查詢運算符中應用非常廣泛,提高了編程效率,且寫出的代碼非常的簡潔。文中若有不足之處,還望海涵,博文寫作不易希望多多支持,後續會更新更多內容,感興趣的朋友可以加關註,歡迎留言交流!