[.net 物件導向程式設計進階] (5) Lamda表示式(一) 建立委託
本節導讀:
通過學習Lambda表示式,學會建立委託和表示式目錄樹,深入瞭解Lambda的特性,讓你的程式碼變的更加清晰、簡潔、高效。
讀前必備:
本節學習前,需要掌握以下知識:
A.泛型 (請參考[.net 物件導向程式設計基礎] (18) 泛型)
B.Linq基礎 (請參照[.net 物件導向程式設計基礎] (19) LINQ基礎)
C.Linq使用 (請參照[.net 物件導向程式設計基礎] (20) LINQ使用)
D.委託 (請參照[.net 物件導向程式設計基礎] (21) 委託)
E.事件 (請參照[.net 物件導向程式設計基礎] (22) 事件)
通過《.net 物件導向程式設計基礎》系列中相關介紹,我們已經初步使用過了Lambda表示式進行Linq查詢,這節我們主要深入瞭解Lambda表示式。
1. 關於Lambda
Lambda 表示式是一種可用於建立委託或表示式目錄樹型別的匿名函式。
以上是微軟對Lambda表示式的定義。從這個定義中,我們可以看出,Lambda的存在,主要做兩件事:
A.建立委託(Delegate)
B.建立表示式樹(Expression Tree)
此外,Lambda表示式的本質就是匿名方法(或叫匿名函式)。
後面面我們分別從這兩個方法入手,進一步學習Lambda帶我給我們的便利。
2. Lambda表示式
Lambda表示式的構成如下:
零個引數: ()=>expr
一個引數:(param)=>expr 或 param=>expr
多個引數:(param-list)=>expr
所有Lambda表示式都使用Lambda運算子=>,該運算子讀作"goes to"
當引數只有一個時,右邊的括號可以省略。
下面是寫法舉例:
//零個引數 () => MethodName() //一個引數 (x) => x+x X=>x+x //兩個及以上引數 (m,n) => m.Length>n //顯式型別引數 (int x,string y)=>y.Length>x
上面的示例中,其中顯式型別引數,是當編譯器無法推斷其引數型別時,可以顯式的定義引數型別。
3. Lambda語句
Lambda語句和Lambda表示式類似,只是右邊部分寫在{}中
Lambda語句構成如下:
(input parameters) => {statement;}
示例:
delegate void TestDelegate(string s); … TestDelegate myDel = n => { string s = n + " " + "World"; Console.WriteLine(s); }; myDel("Hello");
4.非同步Lambda(async和await)
我們通過在《.net 物件導向程式設計基礎》中學習,瞭解了事件本身也是一種特殊的委託,那麼Lambda可用於建立委託也就是說同樣可以來建立事件。
下面看示例:
首先看一個普通按鈕點選事件的實現及Lambda語句寫法
下面是普通寫法:
this.myButton.Click += new System.EventHandler(this.myButton_Click); //事件通常委託寫法 private void myButton_Click(object sender, EventArgs e) { MessageBox.Show("您好,我是按鈕點選事件!"); }
下面是Lambda寫法:
//事件Lambda語句寫法 myButton.Click += (sender, e) => { MessageBox.Show("您好,我是按鈕點選事件!這是Lambda語句寫法"); };
上面兩種寫法,我們在前面的章節中已經有很多例子了。
下面我們看一下非同步的事件:
//非同步事件普通寫法 private async void asyncButton_Click(object sender, EventArgs e) { await MyMethodAsync(); MessageBox.Show("您好,我是按鈕點選事件!非同步的喲!"); } async Task MyMethodAsync() { await Task.Delay(1000); }
我們用Lambda來改寫上面的非同步事件:
//非同步事件Lambda寫法 asyncButton.Click += async (sender, e) => { await MyMethodAsync(); MessageBox.Show("您好,我是按鈕點選事件!非同步的喲!這是Lambda語句寫法"); }; async Task MyMethodAsync() { await Task.Delay(1000); }
上面的非同步事件,就是在事件委託階段使用async來表示這是一個非同步的,在事件處理中階段使用await關鍵詞來指定一個非同步方法。
Lambda語句寫法同樣是針對非同步事件的簡潔寫法,具有相同的效力。
5. 標準查詢運算的Lambda表示式
5.1泛型委託 使用Lambda
我們在[.net 物件導向程式設計基礎] (21) 委託一節中說到了三種常用的泛型委託
Action(無返回值泛型委託)
Func(有返回值泛型委託)
predicate(返回值為bool型的泛型委託)
這節不再重複說明泛型委託,不熟悉泛型委託的小夥伴,請參考,我們只舉例說明泛型委託和它的Lambda寫法
//Action 無返回值型別的 泛型委託 //匿名方法宣告及呼叫 Action<int, int> act = delegate (int a, int b) { Console.WriteLine(a + "+" + b + "=" + (a + b)); }; act(11, 22); //表示式宣告及呼叫 Action<int, int> actLambda = (a, b) => { Console.WriteLine(a + "+" + b + "=" + (a + b)); }; actLambda(111, 222); //Func 帶返回值的 泛型委託 //匿名方法宣告及呼叫 Func<int, int, string> acc = delegate (int a, int b) { return (a + "+" + b + "=" + (a + b)); }; Console.WriteLine(acc(11, 22)); //表示式宣告及呼叫 Func<int, int, string> ac = (a, b) => { return (a + "+" + b + "=" + (a + b)); }; Console.WriteLine(ac(111, 222));
5.2 Linq中使用Lambda
關於在Linq中使用Lambda表示式,我們在[.net 物件導向程式設計基礎] (20) LINQ使用有詳細說明了,不熟悉的小夥伴請參考,下面我們舉例說明幾種常用的Lambda查詢寫法。
5.2.1 Count 求數量
//查詢下列數中的奇數 - Lambda寫法 int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 }; int oddNumbers = numbers.Count(n => n % 2 == 1); Console.WriteLine("奇數有:"+oddNumbers.ToString()+ "個,Lambda寫法"); //查詢下列數中的奇數 -Linq查詢寫法 var oddNumbersFunc = (from num in numbers where num % 2 == 1 select num ).Count(); Console.WriteLine("奇數有:" + oddNumbersFunc.ToString() + "個,Linq查詢寫法");
執行結果如下:
5.2.2 TakeWhile 滿足條件就返回集合
//TakeWhile只要滿足指定的條件就返回,在檢檢到number中的6時,就返回,其中8 和9 不滿足條件,則返回 5 4 1 3 int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 }; var firstNumbersLessThan6 = numbers.TakeWhile(n => n < 6); Console.WriteLine("滿足條件就返回的數有:" + firstNumbersLessThan6.Count().ToString() + "個,Lambda寫法"); //第二個引數index表示n的索引位置 string NewNumber = String.Empty; numbers.TakeWhile((n, index) => n >= index).ToList().ForEach(m=> NewNumber+=m +" "); Console.WriteLine("滿足條件就返回的數有:" + NewNumber + ",Lambda寫法");
執行結果如下:
6. Lambda的型別推理
在編寫 lambda 時,通常不必為輸入引數指定型別,因為編譯器可以根據 lambda 主體、引數的委託型別以及 C# 語言規範中描述的其他因素來推斷型別。 對於大多數標準查詢運算子,第一個輸入是源序列中的元素型別。 因此,如果要查詢 IEnumerable<Customer>,則輸入變數將被推斷為 Customer 物件,這意味著你可以訪問其方法和屬性:
//Lambda的型別推理 List<MyClass> list = new List<MyClass>() { new MyClass(){ at1 = "aaa", at2 = 2, at3 = DateTime.Now}, new MyClass{ at1 = "bbb", at2 = 5, at3 = DateTime.Parse("2015-06-07") }, new MyClass{ at1 = "aaa", at2 = 1, at3 = DateTime.Parse("2010-11-12") } }; Console.WriteLine("at1為aaa的元素有:" + list.Where(m => m.at1 == "aaa").Count() + "個,Lambda寫法");
class MyClass { public string at1 { get; set; } public int at2 { get; set; } public DateTime at3 { get; set; } }
執行結果如下:
Lambda 的一般規則如下:
A. Lambda 包含的引數數量必須與委託型別包含的引數數量相同。
B. Lambda 中的每個輸入引數必須都能夠隱式轉換為其對應的委託引數。
C. Lambda 的返回值(如果有)必須能夠隱式轉換為委託的返回型別。
請注意,lambda 表示式本身沒有型別,因為常規型別系統沒有“Lambda 表示式”這一內部概念。但是,有時以一種非正式的方式談論 lambda 表示式的“型別”會很方便。 在這些情況下,型別是指委託型別或 lambda 表示式所轉換到的 Expression 型別。
7. Lambda表示式的變數範圍
我們在[.net 物件導向程式設計基礎] (19) LINQ基礎 一節中說到匿名方法時提到匿名方法可以引用滿園內的外部變數,前面示例中也有提及。
下面我們看一個示例:
class Program { static int num = 2; static void Main(string[] args) { int num2 = 7; //Lambda表示式的變數範圍 //相對以下表示式內部,一個類的欄位num和一個變數num2,下面測試在表示式內部呼叫 int[] numbers2 = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 }; //我們對大於num小於num2的數求和 int sum = numbers2.Where(m => m > num && m < num2).Sum(); Console.WriteLine("大於num小於num2的和為:" + sum); Console.ReadKey(); } }
下列規則適用於 lambda 表示式中的變數範圍:
A.捕獲的變數將不會被作為垃圾回收,直至引用變數的委託符合垃圾回收的條件。
B.在外部方法中看不到 lambda 表示式內引入的變數。
C.Lambda 表示式無法從封閉方法中直接捕獲 ref 或 out 引數。
D.Lambda 表示式中的返回語句不會導致封閉方法返回。
E.如果跳轉語句的目標在塊外部,則 lambda 表示式不能包含位於 lambda 函式內部的 goto 語句、break 語句或 continue 語句。 同樣,如果目標在塊內部,則在 lambda 函式塊外部使用跳轉語句也是錯誤的。
8.要點:
本節主要說明了:
Lambda表示式在建立委託中的應用;
Lambda表示式在Linq查詢中的應用;
Lambda 語句在非同步事件中的應用;
Lambda 的兩個特性:型別推理和外部變數引用
下一節,我們主要說明Lambda的另一個特點,就是建立表示式目錄樹(Expression Tree)。
==============================================================================================
<如果對你有幫助,記得點一下推薦哦,如有
有不明白或錯誤之處,請多交流>
<對本系列文章閱讀有困難的朋友,請先看《.net 物件導向程式設計基礎》>
<轉載宣告:技術需要共享精神,歡迎轉載本部落格中的文章,但請註明版權及URL>
==============================================================================================