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

    https://www.cnblogs.com/ashleyboy/p/4831064.html

  • 相关阅读:
    Security 前端页面配置
    使用HttpSession获取用户信息
    开启使用Tonken记住我功能
    基于注解的方式发送和订阅消费消息
    通过Security提供的SecurityContextHolder获取登录用户信息
    RabbitMQ 基于API的方式发送和消费消息
    TypeScript declare Object Array Interface methods All In One
    js get Set the first item All In One
    macOS run VSCode from terminal All In One
    在线 Java 语言编程 All In One
  • 原文地址:https://www.cnblogs.com/sjqq/p/8287685.html
Copyright © 2020-2023  润新知