文章目錄
- 目錄
- 1. 使用命名的方法
- 2. 使用匿名方法
- 3. 使用lambda運算式
- 總結
- 內容來源
目錄
- Lambda運算式
- 運算式樹狀架構
- 關鍵字變數var、對象和集合初始化,以及匿名型別
- 擴充方法 ( Extension Method )
- 分部方法 ( Partial Method )
- 查詢運算式
Lambda運算式
在C# 3.0 中,Microsoft 添加了“lambda 運算式”。lamdba運算式曾經用於很久以前的LISP電腦語言中,在1936年由一個美國數學家Alonzo Church對其進行了概念化描述。這些運算式提供了便捷的文法來指定一個演算法。
但是在接下來開始介紹lambda運算式之前,首先看看將一個演算法指定為某個方法的參數的演化過程,因為這正是lambda運算式的目的。
1. 使用命名的方法
在C# 2.0之前,當一個方法或變數要用到委託(delegate)時,開發人員必須建立一個命名方法,並在需要委託的位置傳入這個名稱。例如,考慮以下情況。
假定有兩個開發人員,一個是通用代碼開發人員,而另一個是應用程式開發人員。但是,實際上沒有必要的有兩個不同的開發人員,只需要通過標記來描述兩個不同的角色即可。通用代碼開發人員需要建立通用目的代碼,這些代碼可以在整個工程中被重複使用。應用程式開發人員將使用這些通用目的的代碼來建立一個應用程式。
在這個樣本的情境中,通用代碼開發人員想要建立一個通用方法來篩選整形數組,但是這個通用方法要能夠指定用來篩選該數組的演算法。首先,開發人員必須聲明一個委託 delegate,該委託的設計原型就是收到一個int類型,如果在被篩選的數組中確實包含一個int類型,則這個委託返回true。
於是,開發人員建立了一個工具類並添加了 delegate 和篩選方法。這個公告代碼如下所示:
public class Common{ public delegate bool IntFilter(int i); public static int[] FilterArrayOfInts(int[] ints, IntFilter filter) { ArrayList aList = new ArrayList(); foreach (int i in ints) { if (filter(i)) { aList.Add(i); } } return ((int[])aList.ToArray(typeof(int))); }}
這個公用代碼的開發人員將把 delegate 聲明和 FilterArrayOfInts 方法放到一個公用的庫程式集中,也就是動態連結程式庫(DLL)中,這樣就可以在多個應用程式中使用這個聲明和方法了。
上面列出的 FilterArrayOfInts 方法允許應用程式開發人員將一個整形數組和一個 delegate 作為參數傳遞到篩選方法中,並將獲得一個篩選後的數組。
現在,假定應用程式開發人員只想篩選奇數。則可以參見下面的篩選方法,該方法在開發人員的應用程式代碼中進行了聲明。
public class Application{ public static bool IsOdd(int i) { return ((i & 1) == 1); }}
根據 FilterArrayOfInts 方法中的代碼,傳入數組中的每個 int 整形都會調用這個方法。如果傳送的 int 整形數是奇數,則此篩選方法將只返回true。下面是使用樣本:
int[] nums = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };int[] oddNums = Common.FilterArrayOfInts(nums, Application.IsOdd);foreach (int i in oddNums){ Console.WriteLine(i);}
樣本的運行結果如下:
請注意,要想將 delegate 傳遞為 FilterArrayOfInts 方法的第二個參數,應用程式開發人員只需要傳遞該方法的名稱。只需要簡單的建立另一個篩選器,應用程式開發人員可以篩選不同的資料。應用程式開發人員還可以為偶數、素數,以及任何想要的規則建立篩選器。方法委託(delegate)可以通過這種方式成為具有很高可重用的代碼。
2. 使用匿名方法
上面介紹的方法是很用效而且很好的,但是為所有這些篩選方法和所需的任何其他方法委託編寫代碼是很單調乏味的。在這些方法中,實際上許多方法都只用於單次調用,因此為所有這些方法都建立命名方法是很麻煩的。從C# 2.0開始,開發人員可以使用匿名方法來傳遞內聯代碼,也就是說,用內聯代碼來取代方法委託。匿名方法允許開發人員在通常用來傳遞方法委託的位置指定一段篩選代碼即可。樣本如下:
int[] nums = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };int[] oddNums = Common.FilterArrayOfInts(nums , delegate(int i) { return ((i & 1) == 1); });foreach (int i in oddNums){ Console.WriteLine(i);}Console.Read();
這樣做真的很酷。應用程式開發人員不再需要在任何位置聲明一個方法。這樣做對於將來不需要被重新使用的篩選邏輯代碼而言是非常好的。正如代碼要求的那樣,該樣本的運行結果與上一個樣本相同。
3. 使用lambda運算式
lambda 運算式可以寫成一個逗號分隔的參數列表,後面加一個 lamdba 操作符,在後面是一個運算式或聲明函數。如果有多個輸入參數,則使用圓括弧包含輸入參數。在 C# 中,lambda 操作符可以寫為“=>”。這樣,C# 中的 lambda運算式可以寫成:
(param1, param2, ...paramN) => expr
而在需要用到更複雜的運算式時,可以使用以下函式宣告:
(param1, param2, ...paramN) =>{ statement1; statement2; ... statementN; return(lambda_expression_return_type);}
在這個樣本中,這個函式宣告最後放回的資料類型必須匹配 delegate 指定的放回類型。
下面是 lambda 運算式的有一個簡單樣本:
x => x
這個 lambda 運算式可以讀做“ x 到 x ”,或者讀做“輸入x返回x”。這意味著對於輸入變數x,將會返回x。這個運算式只是用於返回傳入的參數。因為該運算式只用一個輸入參數x,因此其不必包含在圓括弧中。非常重要的是,要知道是 delegate 在指示輸入參數x的類型,以及要返回的值的類型。例如,如果 delegate 被定義為傳入一個string類型的參數,返回一個bool型值,則不能使用x=>x運算式,這是因為如果輸入的參數x 是一個string類型,則返回的值同樣也應該是string,但是,delegate 指定該傳回值必須為 bool 類型。因此,對於被定義為那樣的delegate,運算式中位於lambda操作符(=>)右邊的部分必須等於或返回一個bool型值,比如:
x => x.Length > 0
這個lambda運算式可以讀做“x到x.Length>0”,或則讀做“輸入x返回x.Length > 0”。由於這個運算式的右邊部分等於一個bool型值,則 delegate 應該指定該方法將返回一個bool型值,否則將會編譯錯誤。
下面的 lambda 運算式將返回輸入參數的長度。因此 delegate 應該指定一個int 型傳回值:
s => s.Length
如果要將多個參數傳入到lambda運算式中,可以使用逗號分隔這些參數,並將這些參數包含在圓括弧中,例如:
(x, y) => x == y
複雜的 lambda 運算式甚至要使用函式宣告,例如:
(x, y) =>{ if (x > y)return x; elsereturn y;}
重點要記住的是,delegate 定義了輸入什麼類型的值和必須返回什麼類型的值。因此請確保 lambda 運算式匹配 delegate的定義。
警告: 請確認編寫的 lambda 運算式滿足 delegate 定義指定的輸入類型,並返回 delegate 定義的傳回型別。
要想記得更清楚一點,可以參考下面的公告代碼開發人員定義的 delegate 語句:
public delegate bool IntFilter(int i);
應用程式開發人員的lambda運算式必須支援傳入一個int型參數,並返回一個bool型值。這可以通過該運算式調用的方法和篩選方法的功能來進行推斷,但是,重點要記住的是,這是由 delegate 委託規定的。
如果使用 lambda 運算式,則上面的執行個體可以寫成這樣:
int[] nums = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };int[] oddNums = Common.FilterArrayOfInts(nums, i => ((i & 1) == 1)); foreach (int i in oddNums){ Console.WriteLine(i);}
的確,真是很簡潔的代碼。這段代碼看起來很有趣,因為這些代碼非常新,但是在習慣使用這樣寫代碼後,可以確信這些代碼是可讀和可維護的。正如想要的那種,這段代碼的運行結果與上一個樣本的運行結構相同:
總結
回顧一下,下面列出了每種方法的範例程式碼中的關鍵程式碼:
int[] nums = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };//1.使用命名方法int[] oddNums = Common.FilterArrayOfInts(nums, Application.IsOdd);//2.使用匿名方法int[] oddNums = Common.FilterArrayOfInts(nums , delegate(int i) { return ((i & 1) == 1); });//3.使用lambda運算式int[] oddNums = Common.FilterArrayOfInts(nums, i => ((i & 1) == 1));
從上面的代碼可以看出,第一個方法的程式碼更短一些,但是不要忘記,在代碼的其他位置還必須聲明一個命名方法來具體定義該方法的功能。當然,如果將來在很多位置都會重新使用這個篩選邏輯,或者這個方法的演算法很複雜,並且必須得到特定開發人員的信任,那麼建立一個也可以被其他開發人員使用的命名方法相對來說更有意義。
提示: 對於複雜和需要重用的演算法,最好可以使用命名方法,這樣,任何開發人員都可以很方便地重新使用這些演算法了,並且,開發人員不必詳細瞭解這個演算法。
內容來源
《Pro LINQ Language Integrated Query in C# 2008》——Joseph C. Rattz, Jr.
標籤: C#語言新特性,Lambda