引言:
對於剛剛接觸Lambda表達式的朋友們,可能會對Lambda表達式感到非常疑惑,它到底是個什么什么樣的技術呢?以及它有什么好處和先進的地方呢?下面的介紹將會解除你這些疑惑。
一、Lambda表達式的演變過程
Lambda表達式其實大家可以理解為它是一個匿名函數(對於匿名函數的介紹大家可以參考我這篇文章), Lambda表達式可以包含表達式和語句,並且可以用於創建委托,以及C#編譯器也能將它轉換成表達式樹。
對於Lambda表達式中都會使用這個運算符——“=>”,它讀成“goes to” ,該運算符的左邊為輸入參數,右邊是表達式或者語句塊,下面就看看Lambda表達式是如何來創建委托實例(代碼同時也給出了Lambda表達式從匿名方法的演示過程,從而幫助大家更好的理解Lambda表達式是匿名函數的概念,只不過C#3 中提出的Lambda表達式比匿名函數的使用更加簡潔和直觀了,其實原理都是一樣的, 編譯器同樣會把Lambda表達式編譯成匿名函數,也就是一個名字的方法):
using System; namespace Lambda表達式Demo { class Program { /// <summary> /// Lambda 表達式使用演示 /// </summary> /// <param name="args"></param> static void Main(string[] args) { // Lambda表達式的演變過程 // 下面是C# 1中創建委托實例的代碼 Func<string, int> delegatetest1 = new Func<string, int>(Callbackmethod); // ↓ // C# 2中用匿名方法來創建委托實例,此時就不需要額外定義回調方法Callbackmethod Func<string, int> delegatetest2 = delegate(string text) { return text.Length; }; // ↓ // C# 3中使用Lambda表達式來創建委托實例 Func<string, int> delegatetest3 = (string text) => text.Length; // ↓ // 可以省略參數類型string,把上面代碼再簡化為: Func<string, int> delegatetest4 = (text) => text.Length; // ↓ // 如果Lambda表達式只需一個參數,並且那個參數可以隱式指定類型時, // 此時可以把圓括號也省略,簡化為: Func<string, int> delegatetest = text => text.Length; // 調用委托 Console.WriteLine("使用Lambda表達式返回字符串的長度為: " + delegatetest("learning hard")); Console.Read(); } /// <summary> /// 回調方法 /// 如果使用了Lambda表達式和匿名函數,此方法就不需要額外定義 /// </summary> /// <param name="text"></param> /// <returns></returns> private static int Callbackmethod(string text) { return text.Length; } } }
運行結果為:
上面代碼中都有詳細的演變過程,這里就不多解釋了,希望通過這部分之后,大家可以對Lambda表達式有進一步的理解,其實Lambda表達式就是匿名方法,其中使用Lambda表達式來創建委托實例,我們卻沒有指出創建的委托類型,其中編譯器會幫助我們去推斷委托類型,從而簡化我們創建委托類型所需要的代碼,從而更加簡潔,所以Lambda表達式可以總結為——它是在匿名方法的基礎上,再進一步地簡化了創建委托實例所需要的代碼。
二、Lambda表達式的使用
為了幫助大家更好的理解Lambda表達式,下面演示下用Lambda表達式來記錄事件(代碼中Lambda運算符的右邊調用了一個回調方法ReportEvent()):
using System; using System.Windows.Forms; namespace Lambda表達式來記錄事件Demo { class Program { static void Main(string[] args) { // 新建一個button實例 Button button1 = new Button() { Text ="點擊我"}; // C# 2中使用匿名方法來訂閱事件 //button1.Click+=delegate (object sender,EventArgs e) //{ // ReportEvent("Click事件", sender, e); //}; //button1.KeyPress += delegate (object sender, KeyPressEventArgs e) //{ // ReportEvent("KeyPress事件,即鍵盤按下事件", sender, e); //}; // C# 3Lambda表達式方式來訂閱事件 // 與上面使用匿名方法來訂閱事件是不是看出簡單了很多,並且也直觀了 button1.Click += (sender, e) => ReportEvent("Click事件", sender, e); button1.KeyPress += (sender, e) => ReportEvent("KeyPress事件,即鍵盤按下事件", sender, e); // C# 3之前初始化對象時使用下面代碼 //Form form = new Form(); //form.Name = "在控制台中創建的窗體"; //form.AutoSize = true; //form.Controls.Add(button1); // C# 3中使用對象初始化器 // 與上面代碼的比較中,也可以看出使用對象初始化之后代碼簡化了很多 Form form = new Form { Name = "在控制台中創建的窗體", AutoSize = true, Controls = { button1 } }; // 運行窗體 Application.Run(form); } // 記錄事件的回調方法 private static void ReportEvent(string title, object sender, EventArgs e) { Console.WriteLine("發生的事件為:{0}", title); Console.WriteLine("發生事件的對象為:{0}", sender); Console.WriteLine("發生事件參數為: {0}", e.GetType()); Console.WriteLine(); Console.WriteLine(); } } }
運行結果:
從上面代碼中可以看出,使用Lambda表達式之后代碼確實簡潔了很多,上面代碼中都有詳細的注釋,這里就不解釋了,大家可以查看代碼中的注釋來進行理解,並且代碼中注釋部分也列出了C# 3之前是如何實現這樣的代碼的, 這樣有利於比較,從而幫助大家更好的認識到Lambda所帶來的好處和進一步來理解Lambda表達式。
三、表達式樹
上面指出Lambda表達式除了可以用來創建委托外,C#編譯器還可以將他們轉換成表達式樹——用於表示Lambda表達式邏輯的一種數據結構,表達式樹也可以稱作表達式目錄樹,它將代碼表示成一個對象樹,而不是可執行的代碼。對於剛接觸哦表達式樹的朋友肯定會問——為什么需要把Lambda表達式轉化為表達式目錄樹呢?對於表達式樹的提出主要是為后面Linq to SQL 做鋪墊,一個Linq to SQL 的查詢語句並不是在C#的程序中執行的,而是C#編譯器把它轉化為SQL 語句,然后再在數據庫中執行。在我們使用Linq to SQL的時候都需要添加一個Linq to SQL的類,該類的擴展名dbml,該的作用就是幫助我們把Linq to SQL 的語句映射為SQL語句,然后再在數據庫中執行SQL語句,把返回的結果再返回給一個IQueryable集合,所以Linq to SQL 也采用了通常的ORM(Object—Relationship—Mapping)來設計的,相當於是一個ORM框架,不過這個框架只能與微軟的SQL server數據庫進行映射,對於其他類型的數據庫卻不可以,然而很多其他開發人員卻對此進行了一些擴展,擴展了對其他數據庫的支持。前不久還在博客園中發布了開源的Linq框架的,名字為ELinq,其他它就是對Linq to SQL的一個擴展,使Linq語句可以映射到其他數據庫的查詢語句。
下面先看看如何把Lambda表達式轉化為表達式目錄樹(其中需要引入一個新的命名空間—— System.Linq.Expressions):
using System; // 引用額外的命名空間 using System.Linq.Expressions; namespace 表達式樹Demo { class Program { /// <summary> /// 表達式樹的演示 /// </summary> /// <param name="args"></param> static void Main(string[] args) { #region 將Lambda表達式轉換為表達式樹演示 // 將Lambda表達式轉換為Express<T>的表達式樹 // 此時express不是可執行的代碼,它現在是一個表達式樹的數據結構 Console.WriteLine("將Lambda表達式轉化為表達式樹的演示:"); Expression<Func<int, int, int>> expression = (a, b) => a + b; // 獲得表達式樹的參數 Console.WriteLine("參數1: {0},參數2:{1}", expression.Parameters[0],expression.Parameters[1]); // 既然叫做樹,那肯定有左右節點 // 獲取表達式樹的主體部分 BinaryExpression body = (BinaryExpression)expression.Body; // 左節點,每個節點本身就是一個表達式對象 ParameterExpression left = (ParameterExpression)body.Left; // 右節點 ParameterExpression right = (ParameterExpression)body.Right; Console.WriteLine("表達式主體為:"); Console.WriteLine(expression.Body); Console.WriteLine("表達式樹左節點為:{0}{4} 節點類型為:{1}{4}{4} 表達式右節點為:{2}{4} 節點類型為:{3}{4}", left.Name, left.NodeType, right.Name, right.NodeType,Environment.NewLine); Console.Read(); #endregion #region 把表達式樹轉化回可執行代碼 // Compile方法生成Lambda表達式的委托 Console.WriteLine("按下Enter鍵進入將表達式樹轉換為Lambda表達式的委托演示:"); int result = expression.Compile()(2, 3); Console.WriteLine("調用Lambda表達式委托結果為:" + result); Console.ReadKey(); #endregion } } }
運行結果:
上面代碼首先把Lambda表達式轉化為表達式樹,下面這行代碼就是把Lambda表達式轉化為表達式樹:
Expression<Func<int, int, int>> expression = (a, b) => a + b;
之后對於表達式樹這種數據結構進行分析來獲得該樹中的主體和左右節點是什么,獲得主體和左右節點的代碼如下:
// 獲取表達式樹的主體部分 BinaryExpression body = (BinaryExpression)expression.Body; // 左節點,每個節點本身就是一個表達式對象 ParameterExpression left = (ParameterExpression)body.Left; // 右節點 ParameterExpression right = (ParameterExpression)body.Right;
從上面代碼可以得出——樹中的每個節點都是一個表達式(ParameterExpression和BinaryExpression都是繼承Expression的,所以左右節點都是表達式),分析完表達式樹之后,代碼中還演示了如果把表達式樹轉化為可執行的代碼,即轉化為Lambda表達式的委托對象(此時調用Expression的Compile()方法來轉化為可執行代碼),通過調用委托來獲得結果。
關於Lambda表達式樹的更多信息還可以參看這篇博客:http://www.cnblogs.com/tianfan/archive/2010/03/05/expression-tree-basics.html (博主翻譯的還可以)
四、總結
到這里本專題的內容也介紹的差不多了,希望通過本專題使一些之前對Lambda表達式感到疑惑的朋友們現在可以理解Lambda表達式,因為只有理解好Lambda表達式之后,對於Linq的學習就可以說是輕而易舉了。
補充:
1.匿名函數不等於匿名方法,匿名函數包含了匿名方法和lambda表達式這兩種概念。
匿名函數:{匿名方法,lambda表達式}
lambda作為表達式,可以被C#編譯器轉換為委托,也可以被編譯器轉換為表達式樹,匿名方法只能轉換為委托。
兩者的共通點是都能被編譯器轉換成為委托,lambda表達式能完成幾乎所有匿名方法能完成的事。
作為委托和表達式樹,兩者在IL階段表示就不一樣了。作為委托的IL,在運行期間直接被CLR所執行,而作為表達式樹,是不被CLR所直接執行,而是通過相應的Provider轉換為所需要的東西,比如說可以轉換為SQL,也可以轉換為JAVA。(引自留言中浪雪朋友的意見)
本專題中演示源碼:http://files.cnblogs.com/zhili/Lambda%E8%A1%A8%E8%BE%BE%E5%BC%8FDemo.zip