[.net 物件導向程式設計進階] (5) Lamda表示式(一) 建立委託

yubinfeng發表於2015-07-03

[.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>

.NET 技術交流群:467189533    .NET 程式設計

==============================================================================================   

相關文章