lambda表達式是C#3.0中引入的比匿名方法更加簡潔的一種語法,可用於創建委托或表達式樹類型的匿名函數。Lambda表達式本身可划分為兩種類型:語句Lambda和表達式Lambda。
表達式Lambda:
要創建lambda表達式,則在lambda運算符 => 的左側指定輸入的參數(如果存在參數),Lambda表達式返回表達式的結果,基本形式如下:
(input parameters) => expression
只有lambda有一個參數時,括號是可以省略的,否則括號是必須的,兩個及兩個以上參數以逗號分隔:
(x, y) => x == y; //參數以逗號分隔 x=>x++; ()=>true; //使用空括號指定零個輸入參數
語句Lambda:
lambda語句與lambda表達式類似,只是把語句括在大括號中,Lambda 語句的主體可以包含任意數量的語句:
(input parameters) => {statement;}
Lambda表達式傳遞委托:
使用Lambda表達式使代碼更為簡潔,以下示例:
public delegate int DelegateTest(int n1, int n2); static void Main(string[] args) { //委托:方法作為參數傳遞 var r1 = Result(3, 4, Sum); //使用匿名方法傳遞委托 var r4 = Result(3,4,delegate(int x,int y){return x+y;}); //語句lambda傳遞委托 var r2 = Result(3, 4, (a, b) => { return a - b; });
//lambda傳遞委托 var r3 = Result(3, 4, (a, b) => a * b); Console.ReadLine(); } public static int Result(int a, int b, DelegateTest @delegate) { return @delegate(a, b); } public static int Sum(int a, int b) { return a + b; }
上面的代碼可以看出來,lambda其實和匿名方法沒什么區別,使用lambda表達式創建的委托實例,不需要顯示定義方法,簡化了代碼,所以說lambda表達式的使用使的委托實例的創建更加簡潔和直觀。
在編寫lambda時,一般不需要為輸入參數指定類型,比如(a, b) => a * b,因為編譯器可以根據lambda主體、基礎委托類型等因素推斷類型,lambda表達式一般遵循以下幾個規則:
1.lambda包含的參數數量必須與委托類型包含的參數數量一致。
2.lambda中每個輸入參數必須都能夠隱式轉換為其對應的委托參數(逆變性)
3.lambda的返回值(如果有返回值)必須能夠隱式轉換為委托的返回類型(協變性)
Lambda表達式的內部機制:
Lambda表達式並非CLR內部的固有構造,它們的實現是由C#編譯器在編譯時生成的。Lambda表達式為“以內嵌方式聲明委托”模式提供一個對應的C#與語言構造。所以,當我們編寫lambda時,編譯器實際上會生成一系列代碼,就以上面的代碼為例,通過反編譯工具查看生成的等價代碼:
Main方法中使用lambda表達式傳遞委托,編譯后生成的是匿名函數。這也證明了上面說的lambda其實就是簡化了的匿名函數。
再來看看上面的匿名方法 delegate(int x,int y){return x+y;}和lambad表達式(a, b) => { return a - b; }、(a, b) => a * b),編譯后實際生成了3個靜態方法 <Main>b_0,<Main>b_1和<Main>b_2,也就是說,在調用的時候還是由這3個靜態方法去分別實例化成委托,並作為參數傳遞的,又回到了最初對委托的了解:委托實現把方法作為參數傳入到另一個方法。
表達式樹
表達式樹是一種允許將lambda表達式表示為樹狀數據結構而不是可執行邏輯的代碼。
表達式樹的創建:
1.通過Lambda表達式創建表達式樹
下面的代碼演示將lambda表達式表示為可執行代碼和表達式樹:
Func<int, int> res = x => x + 1; //Code Expression<Func<int, int>> exp = x => x + 1; //Data
進行上面的賦值之后,委托res引用返回x+1的方法,表達式樹exp引用描述表達式x=>x+1的數據結構,這是兩者的明顯區別。
2.通過API創建表達式樹
要使用API創建表達式樹,需要使用Expression類。該類提供創建特定類型的表達式樹節點的靜態方法,例如:ParameterExpression(表示一個變量或參數)或MethodCallExpression(表示一個方法調用)。下面演示使用API創建一個表達式樹:
static void Main(string[] args) {//創建表達式樹:Expression<Func<int, int>> exp = x => x + 1; ParameterExpression param = Expression.Parameter(typeof(int),"x"); ConstantExpression value = Expression.Constant(1, typeof(int)); BinaryExpression body = Expression.Add(param, value); Expression<Func<int, int>> lambdatree = Expression.Lambda<Func<int, int>>(body, param); Console.WriteLine("參數param:{0}", param); Console.WriteLine("描述body:{0}", body); Console.WriteLine("表達式樹:{0}", lambdatree); //解析表達式樹: //取得表達式樹的參數 ParameterExpression dparam = lambdatree.Parameters[0] as ParameterExpression; //取得表達式樹描述 BinaryExpression dbody = lambdatree.Body as BinaryExpression; //取得節點 ParameterExpression left = dbody.Left as ParameterExpression; ConstantExpression right = body.Right as ConstantExpression; Console.WriteLine("解析出的表達式:{0}=>{1} {2} {3}", param.Name, left.Name, body.NodeType, right.Value); Console.ReadLine(); }
運行結果:
3.表達式樹的修改和執行
表達式樹是不可變的,就是說如果要修改某個表達式樹,必須通過復制現有的表達式並替換其中的節點,重新構造一個新的表達式樹。具體可以使用ExpressionVisitor類遍歷現有表達式樹,並復制它的每個節點。
public class NewExpression : ExpressionVisitor { public ParameterExpression param { get; set; } public NewExpression(ParameterExpression param) { this.param = param; } public Expression Replace(Expression exp) { return this.Visit(exp); } protected override Expression VisitParameter(ParameterExpression node) { //return base.VisitParameter(node); return this.param; } } class LambdaProgram {static void Main(string[] args) { //x=>x>5 和 x=>x<10 --> x=> x>5 && x<10 Expression<Func<int, bool>> exp1 = x => x > 5; Expression<Func<int, bool>> exp2 = x => x < 10; ParameterExpression y = Expression.Parameter(typeof(int), "y"); var newExp = new NewExpression(y); var newexp1 = newExp.Replace(exp1.Body); var newexp2 = newExp.Replace(exp2.Body); var newbody = Expression.And(newexp1, newexp2); Expression<Func<int, bool>> res = Expression.Lambda<Func<int, bool>>(newbody, y); //將表達式樹描述的lambda表達式編譯為可執行代碼,並生成表示該lambda表達式的委托 Func<int, bool> del = res.Compile(); Console.WriteLine(del(7)); Console.ReadLine(); } }
本來以為只需要將表達式樹exp1 和 exp2中的Body取出重新構建一個表達式樹即可,但實際過程中遇到了問題,查了查,博客園中有這方面問題的解釋:參考了一下http://blog.zhaojie.me/2009/09/specification-pattern-in-csharp-practice-answer-2.html 可以知道,exp1和exp2雖然形式相同,但是它們的“參數”不是同一個對象(此x非彼x呀),所以直接取出exp1和exp2的Body做 var newbody1 = Expression.Add(exp1.Body, exp2.Body) 操作,理想出現的是:x>5 && x<10,實際是報錯的。也就是說 exp1.Body和exp2.Body並沒有公用一個ParameterExpression實例。因此必須將兩個表達式的參數統一成一個對象。實現方法:
public class NewExpression : ExpressionVisitor { public ParameterExpression param { get; set; } public NewExpression(ParameterExpression param) { this.param = param; } public Expression Replace(Expression exp) { return this.Visit(exp); } protected override Expression VisitParameter(ParameterExpression node) { //return base.VisitParameter(node); return this.param; } }
通過重寫的VisitParameter方法,返回的是我們自定義的ParameterExpression對象,當調用Visit方法后,返回的是一個新的NewExpression對象,參數為自定義的ParameterExpression y。
最后執行表達式樹,只能執行表示lambda表達式的表達式樹,表示lamdba表達式的表達式樹屬於LambdaExpression或Expression<TDelegate>類型。執行后可能返回一個值,也可能返回一個委托對象。
Func<int, bool> del = res.Compile(); Console.WriteLine(del(7));
關於表達式樹:表達式樹這一概念的引入,使得程序可以將一個lambda表達式編譯成數據來表示,而不是編譯成一個表示為委托的具體實現(靜態方法)。利用這一特性,Linq to Sql和Linq to Xml等庫能解釋表達式樹,並在CIL之外的上下文中使用。