Linq快速入門——Lambda表達(dá)式的前世今生
Lambda表達(dá)式其實(shí)并不陌生,他的前生就是匿名函數(shù),所以要談Lambda表達(dá)式,就不得不談匿名函數(shù),要談匿名函數(shù),那又要不得不談委托。
何為委托
委托非常好理解,類似于C++里面的函數(shù)指針(指向了一個(gè)方法),并且委托約束了待指向方法的簽名(由返回類型和參數(shù)組成)。
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace 委托Test { delegate bool FilterDelegate(int i); class Program { static void Main(string[] args) { int[] array = { 1, 2, 3, 5, 6, 6, 7, 8, 9 }; List<int> newList = MyFilter(array,FilterOdd); foreach (int item in newList) { Console.WriteLine(item); } Console.ReadKey(); } static List<int> MyFilter(int[] array, FilterDelegate filter) { List<int> list = new List<int>(); for (int i = 0; i < array.Length; i++) { if (filter(i)) { list.Add(i); } } return list; } /// <summary> /// 偶數(shù) /// </summary> /// <param name="i"></param> /// <returns></returns> static bool FilterEven(int i) { return i % 2 == 0; } /// <summary> /// 奇數(shù) /// </summary> /// <param name="i"></param> /// <returns></returns> static bool FilterOdd(int i) { return i % 2 == 1; } } }
對(duì)于上面這個(gè)Demo可以看出,我需要定義了兩個(gè)方法(FilterOdd,F(xiàn)ilterEven),讓我的委托變量指向這兩個(gè)方法。但有時(shí)候申明方法很麻煩,還要考慮方法名稱不重復(fù),所以對(duì)于一些我們只使用一次的方法,完全沒(méi)有必要單獨(dú)為其申明,使用匿名方法即可(C# 2.0為程序員提供了匿名方法),大大簡(jiǎn)化了操作
匿名方法
//例如 delegate void Del(int x); .... Del d = delegate(int k) { /* ... */ };
所以上面例子小小改動(dòng)一下即可:
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace 委托Test { delegate bool FilterDelegate(int i); class Program { static void Main(string[] args) { int[] array = { 1, 2, 3, 5, 6, 6, 7, 8, 9 }; //使用匿名方法來(lái)求偶數(shù) List<int> newList = MyFilter(array, delegate(int i) { return i % 2 == 0; }); foreach (int item in newList) { Console.WriteLine(item); } Console.ReadKey(); } static List<int> MyFilter(int[] array, FilterDelegate filter) { List<int> list = new List<int>(); for (int i = 0; i < array.Length; i++) { if (filter(i)) { list.Add(i); } } return list; } } }
Lambda表達(dá)式特性
- C# 2.0中加入的匿名方法,簡(jiǎn)化了我們編寫事件處理函數(shù)的工作,使我們不再需要單獨(dú)聲明一個(gè)函數(shù)來(lái)與事件綁定,只需要使用delegate關(guān)鍵字在線編寫事件處理代碼。
- 而C# 3.0則更進(jìn)一步,通過(guò)Lambda表達(dá)式,我們可以一種更為簡(jiǎn)潔方式編寫事件處理代碼,新的Lambda事件處理代碼看上去就像一個(gè)計(jì)算表達(dá)式,它使用"=>"符號(hào)來(lái)連接事件參數(shù)和事件處理代碼。我可以這樣寫:SomeEvent += 事件參數(shù) => 事件處理代碼;
所以上面代碼稍稍修改后,用Lambda表達(dá)式來(lái)替換匿名方法:
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace 委托Test { delegate bool FilterDelegate(int i); class Program { static void Main(string[] args) { int[] array = { 1, 2, 3, 5, 6, 6, 7, 8, 9 }; //使用Lambda表達(dá)式來(lái)求偶數(shù) List<int> newList = MyFilter(array, i => i % 2==0); foreach (int item in newList) { Console.WriteLine(item); } Console.ReadKey(); } static List<int> MyFilter(int[] array, FilterDelegate filter) { List<int> list = new List<int>(); for (int i = 0; i < array.Length; i++) { if (filter(i)) { list.Add(i); } } return list; } } }
注意:
- 使用Lambda表達(dá)式,"=>"之前為參數(shù)列表,如果有多個(gè)參數(shù),則不能省略括號(hào),比如:(s,e)=>....
- 如果方法有返回值,并且處理代碼只有一行,可以簡(jiǎn)寫成i=>i%2==0,等價(jià)于i=>{return i%2==0},反之對(duì)于有多行的處理代碼,則不能簡(jiǎn)寫,必須寫完整,比如:(s,e)=>{...程序代碼塊...}
我們?cè)賮?lái)看看System.Linq名稱空間下的擴(kuò)展方法有什么特征:

第一個(gè)參數(shù)為擴(kuò)展方法,我已經(jīng)在前一篇文章《Linq快速入門——擴(kuò)展方法》里提到了,我不做具體解釋了,簡(jiǎn)單來(lái)說(shuō)創(chuàng)建擴(kuò)展方法就是這四步:
- 創(chuàng)建一個(gè)名為MyHelper的類,約定了此類中的方法均是擴(kuò)展方法。注意這個(gè)類必須是靜態(tài)類(Static)
- 擴(kuò)展方法必須是Static靜態(tài)方法
- 第一個(gè)參數(shù)為待擴(kuò)展的類型,前面標(biāo)注this
- 如果MyHelper在一個(gè)類庫(kù)中,記得對(duì)其添加引用并using相關(guān)名稱空間
對(duì)于第二個(gè)參數(shù):System.Func<TSource, bool> predicate),我們?cè)賮?lái)深究下。
Fun<T,TResult> and Action<T>
- Fun<T,TResult>:此委托封裝一個(gè)具有一個(gè)參數(shù)并返回 TResult 參數(shù)指定的類型值的方法。所以在使用 Func<T, TResult> 委托時(shí),不必顯式定義一個(gè)封裝只有一個(gè)參數(shù)的方法并且其返回類型TResut的委托。
- Action<T>:此委托封裝一個(gè)方法,該方法只有一個(gè)參數(shù)并且不返回值。所以在使用 Action<T> 委托時(shí),不必顯式定義一個(gè)封裝只有一個(gè)參數(shù)的方法(并且不能返回值)的委托。
所以再對(duì)上面的Filter進(jìn)行改進(jìn):
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace 委托Test { //delegate bool FilterDelegate(int i); class Program { static void Main(string[] args) { int[] array = { 1, 2, 3, 5, 6, 6, 7, 8, 9 }; //使用匿名方法來(lái)求偶數(shù) //List<int> newList = MyFilter(array, delegate(int i) { // return i % 2 == 0; //}); //使用Lambda表達(dá)式求偶數(shù) List<int> newList = MyFilter(array, i => i % 2 == 0); foreach (int item in newList) { Console.WriteLine(item); } Console.ReadKey(); } //Func<int,bool>: 封裝了一個(gè)具有一個(gè)int參數(shù)并且返回類型為bool類型的方法 static List<int> MyFilter(int[] array,Func<int,bool> filter) { List<int> list = new List<int>(); for (int i = 0; i < array.Length; i++) { if (filter(i)) { list.Add(i); } } return list; } } }
回顧,A Simple Lambda Demo
- 下面Demo首先申明 Func<T, TResult> 變量,并為其分配了一個(gè) lambda 表達(dá)式。
- 隨后將封裝此方法的委托(看下面實(shí)例)傳遞給Enumerable.Where、Enumerable.Order、 Enumerable.Select 方法,以將字符串?dāng)?shù)組中的字符串進(jìn)行處理。
- ForEach 和 ForEach<T> 方法都采用 Action<T> 委托作為參數(shù)。 通過(guò)使用由委托封裝的方法,可以對(duì)數(shù)組或列表中的每個(gè)元素執(zhí)行操作
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace LambdaDemo { class Program { static void Main(string[] args) { string[] names = {"Eyes","Voodoo","Tod","Chris","Christina","Maxisim" }; Func<string, bool> filter = s => s.Length > 5; Func<string, string> order = s => s; Func<string, string> operating = s => s.ToUpper(); IEnumerable<string> expr = names.Where(filter).OrderByDescending(order).Select(operating); expr.ToList<string>().ForEach(i => Console.WriteLine(i)); Console.ReadKey(); } } }
Lambda表達(dá)式樹(shù)
- 表達(dá)式樹(shù)表示樹(shù)狀數(shù)據(jù)結(jié)構(gòu)的代碼,樹(shù)狀結(jié)構(gòu)中的每個(gè)節(jié)點(diǎn)都是一個(gè)表達(dá)式,例如一個(gè)方法調(diào)用或類似 x < y 的二元運(yùn)算。
- 并且你可以編譯和運(yùn)行由表達(dá)式樹(shù)所表示的代碼。這樣的優(yōu)勢(shì)就是表達(dá)式樹(shù)可以在運(yùn)行的時(shí)候編譯運(yùn)行,而且可以對(duì)lambda表達(dá)式進(jìn)行動(dòng)態(tài)修改。
- 若要使用 API 創(chuàng)建表達(dá)式樹(shù),請(qǐng)使用 Expression 類。 此類包含創(chuàng)建特定類型的表達(dá)式樹(shù)節(jié)點(diǎn)的靜態(tài)工廠方法,例如,ParameterExpression(表示一個(gè)變量或參數(shù)),ConstantExpression(表示一個(gè)常量),MethodCallExpression(表示一個(gè)方法調(diào)用)。 ParameterExpression 、MethodCallExpression、ConstantExpression 以及其他表達(dá)式特定的類型也在 System.Linq.Expressions 命名空間中定義。 這些類型派生自抽象類型 Expression。
例如將表達(dá)式(Price-5)*Count*Rebate表示成一棵二叉樹(shù)可以用以下方式表達(dá):

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Linq.Expressions; namespace Lambda表達(dá)式樹(shù) { class Program { static void Main(string[] args) { //計(jì)算(Price-5)*Count*Rebate ParameterExpression paraPrice = Expression.Parameter(typeof(decimal),"price"); ConstantExpression constant = Expression.Constant(5m,typeof(decimal)); BinaryExpression result1 = Expression.Subtract(paraPrice, constant); ParameterExpression paraCount = Expression.Parameter(typeof(decimal),"count"); ParameterExpression paraRebate = Expression.Parameter(typeof(decimal),"rebate"); BinaryExpression result2 = Expression.Multiply(paraCount,paraRebate); BinaryExpression result3 = Expression.Multiply(result1,result2); Expression<Func<decimal, decimal, decimal, decimal>> totalPrice = Expression.Lambda<Func<decimal, decimal, decimal, decimal>>(result3,paraPrice,paraCount,paraRebate); Func<decimal, decimal, decimal, decimal> myFun = totalPrice.Compile(); Console.WriteLine(myFun(125m,10m,0.5m)); Console.ReadKey(); } } }
分析表達(dá)式樹(shù)
Expression<TDelegate> 類型提供 Compile 方法,該方法將表達(dá)式樹(shù)表示的代碼編譯成一個(gè)可執(zhí)行委托。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Linq.Expressions; namespace ConsoleApplication2 { class Program { static void Main(string[] args) { Expression<Func<int, int>> f1 = x => x + 1; //f1(1)//...錯(cuò)誤,必須將表達(dá)式樹(shù)表示的代碼編譯成一個(gè)可執(zhí)行委托 Func<int, int> f2 = f1.Compile(); Console.WriteLine(f2(2)); Console.ReadKey(); } } }
總結(jié)
未完,持續(xù)更新中

浙公網(wǎng)安備 33010602011771號(hào)