Lambda表達式和表達式樹


     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之外的上下文中使用。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM