• [.net 面向对象程序设计进阶] (7) Lamda表达式(三) 表达式树高级应用


    [.net 面向对象程序设计进阶] (7) Lamda表达式(表达式树高级应用

    本节导读:讨论了表达式树的定义和解析之后,我们知道了表达式树就是并非可执行代码,而是将表达式对象化后的数据结构。是时候来引用他解决问题。而本节主要目的就是使用表达式树解决实际问题。 

    读前必备: 

    本节学习前,需要掌握以下知识: 

    A.继承     [.net 面向对象编程基础]  (12) 面向对象三大特性——继承 

    B.多态     [.net 面向对象编程基础]  (13) 面向对象三大特性——多态 

    C.抽象类 [.net 面向对象编程基础]  (15) 抽象类 

    D.泛型    [.net  面向对象编程基础]    (18) 泛型

    1.动态创建表达式树 

    上一节中通过对表达式树结构和解析表达式的学习以后,动态创建表达式树,已经变得非常简单了,下面我们使用表达式树动态创建下节的Lambda表达式. 

    先看我们要最终完成的原表达式: 

    Expression<Func<int, int, bool>> expression = (x, y) => x!=0 && x==y+1;

     动态创建过程如下:

    //动态创建表达式树
    Expression<Func<int, int, bool>> expression = (x, y) => x != 0 && x == y + 1;
    
    //先创建两个参数
    ParameterExpression[] parameters = new ParameterExpression[] { Expression.Parameter(typeof(int),"x"), Expression.Parameter(typeof(int), "y") };
    
    ParameterExpression param1 = parameters[0];
    ParameterExpression param2 = parameters[1];
    
    //下面先创建右边第一个表达式 x!=0
    //(1)常量 0x 
    ConstantExpression rightLeftRight = Expression.Constant(0, typeof(int));
    //(2)创建左边第一个表达式 x!=0
    BinaryExpression rightLeft = Expression.NotEqual(param1, rightLeftRight);
    
    //下面创建右边第二个表达式 x==y+1
    //(3) 先创建右边部分表达式y+1
    BinaryExpression rightRightRight = Expression.Add(param2, Expression.Constant(1, typeof(int)));
    //(4)创建右边表达式  x==y+1
    BinaryExpression rightRight = Expression.Equal(param1, rightRightRight);
    
    //5)创建表达式 右部整体 x != 0 && x == y + 1
    BinaryExpression Right = Expression.AndAlso(rightLeft, rightRight);
    
    //(6)完成整个表达式
    Expression<Func<int, int, bool>> lambdaExr = Expression.Lambda<Func<int, int, bool>>(Right,parameters);
    
    Console.Write(lambdaExr.ToString());

    运行结果如下:

    上面创建过程如下:

    2.执行表达式树

    动态创建完成上面的表达式,我们肯定最终结果是要使用这个表达式进行处理一些问题,对于动创建的表达式树如何执行呢?

    这个问题非常容易,只需要两个步聚:

    A.Compiling 编程表达式树为委托

    B.调用表达式树(调用该委托)

    下面看示例:

    //执行表达式树
    Expression<Func<int, int, bool>> expression = (x, y) => x != 0 && x == y + 1;
    Func<int, int, bool> result = expression.Compile();
    bool result2= expression.Compile()(9,8);
    Console.WriteLine(result2);
    Console.WriteLine(result(3, 2));
    Console.WriteLine(result(5, 4));
    Console.WriteLine(result(6, 4));
    Console.WriteLine(result(-6, -7));

    运行结果如下:

    3.调试表达式树

    在调试应用程序时,您可以分析表达式树的结构和内容。 若要快速了解表达式树结构,您可以使用 DebugView 属性,该属性仅在调试模式下可用。 使用 Visual Studio 进行调试。为了更好地表示表达式A.树的内容,DebugView 属性使用 Visual Studio 可视化工具。

    在“数据提示”、“监视”窗口、“自动”窗口或“局部变量”窗口中,单击表达式树的 DebugView 属性旁边显示的放大镜图标。将会显示可视化工具列表。

    B.单击要使用的可视化工具。

    比如我们使用文本可视化工具

    $符号,表示 参数

    4.修改表达式树

    表达式树是不可变的,这意味着不能直接修改表达式树。

    若要更改表达式树,必须创建现有表达式树的一个副本,并在创建副本的过程中执行所需更改。 您可以使用 ExpressionVisitor 类遍历现有表达式树,并复制它访问的每个节点。 

    .NET 有一ExpressionVisitor 类提供重写来修改表达式树

    下面我们看一下如何通过重写VisitBinary方法将表达式树左边节点类型由 && 转为 ||,实现如下:

    public class OrElseModifier : ExpressionVisitor
    {
        public Expression Modify(Expression expression)
        {
            return Visit(expression);
        }
        protected override Expression VisitBinary(BinaryExpression b)
        {
            if (b.NodeType == ExpressionType.AndAlso)
            {
                Expression left = this.Visit(b.Left);
                Expression right = this.Visit(b.Right);
    
                return Expression.MakeBinary(ExpressionType.OrElse, left, right, b.IsLiftedToNull, b.Method);
            }
    
            return base.VisitBinary(b);
        }
    }

    调用如下:

    //修改表达式树            
    Expression<Func<int, int, bool>> expression = (x, y) => x != 0 && x == y + 1;
    
    OrElseModifier amf = new OrElseModifier();
    Expression newExp= amf.Modify(expression);
    Console.WriteLine("原表达式:      "+ expression.ToString());
    Console.WriteLine("修改后的表达式:"+newExp.ToString());

    运行结果如下:

    对于上面的实现,有几点要说明,上面.NET提供给我们的类ExpressionVisitor 有很多可重写的方法供我们完成对表达式的间接修改,返回一个表达式副本,也就是新的表达式。

    我们在调用阶段为什么要使用Modify(expression);来调用,这点,.net在设计的时候,使用了一种设计模式,就是访问者模式。

    我们可以看到VisitBinary是一个保护的成员,当然我们在重写的时候是不能修改原方法的修饰符的。

    这一点小伙伴们在[.net 面向对象编程基础]  (13) 面向对象三大特性——多态一节中可以详细了解。

    对于设计模式,我如果有时间,会写这方面的东西,博客园相关的文章也是非常多。  

    5.使用表达式树来生成动态查询

    我们做一个有意思的示例,分类查询我在博客园中的文章。

    第一步,我们先获取文章列表,通过一个实体列表来存放数据

    先建立实体:

    /// <summary
    /// 我的博客文章实体类
    /// </summary>
    public class MyArticle
    {
        /// <summary>
        /// 文章编号
        /// </summary>
        public int id { get; set; }
        /// <summary>
        /// 文章标题
        /// </summary>
        public string title { get; set; }
    
        /// <summary>
        /// 文章摘要
        /// </summary>
        public string summary { get; set; }
    
        /// <summary>
        /// 发布时间
        /// </summary>
        public DateTime published { get; set; }
        /// <summary>
        /// 最后更新时间
        /// </summary>
        public DateTime updated { get; set; }
        /// <summary>
        /// URL地址
        /// </summary>
        public string link { get; set; }
        /// <summary>
        /// 推荐数
        /// </summary>
        public int diggs { get; set; }
        /// <summary>
        /// 浏览量
        /// </summary>
        public int views { get; set; }
    
        /// <summary>
        /// 评论数
        /// </summary>
        public int comments { get; set; }
    
        /// <summary>
        /// 作者
        /// </summary>
        public string author { get; set; }
    }

    接下来获取文章

    //动态查询 我在博客园中的文章分类查询
    
    //第一步,获取我在博客园中的文章
    List<MyArticle> myArticleList = new List<MyArticle>();            
    var document = XDocument.Load(
        "http://wcf.open.cnblogs.com/blog/u/yubinfeng/posts/1/100"
        );
    
    var elements = document.Root.Elements();
    
    //在进行这个工作之前,我们先获取我博客中的文章列表
    var result = elements.Where(m => m.Name.LocalName == "entry").Select(myArticle => new MyArticle
    {
        id = Convert.ToInt32(myArticle.Elements().SingleOrDefault(x => x.Name.LocalName == "id").Value),
        title = myArticle.Elements().SingleOrDefault(x => x.Name.LocalName == "title").Value,
        published = Convert.ToDateTime(myArticle.Elements().SingleOrDefault(x => x.Name.LocalName == "published").Value),
        updated = Convert.ToDateTime(myArticle.Elements().SingleOrDefault(x => x.Name.LocalName == "updated").Value),
        diggs = Convert.ToInt32(myArticle.Elements().SingleOrDefault(x => x.Name.LocalName == "diggs").Value),
        views = Convert.ToInt32(myArticle.Elements().SingleOrDefault(x => x.Name.LocalName == "views").Value),
        comments = Convert.ToInt32(myArticle.Elements().SingleOrDefault(x => x.Name.LocalName == "comments").Value),
        summary = myArticle.Elements().SingleOrDefault(x => x.Name.LocalName == "summary").Value,
        link = myArticle.Elements().SingleOrDefault(x => x.Name.LocalName == "link").Attribute("href").Value,
        author = myArticle.Elements().SingleOrDefault(x => x.Name.LocalName == "author").Elements().SingleOrDefault(x => x.Name.LocalName == "name").Value
    });
    myArticleList.AddRange(result);

    创建一个查询表达式树的方法

    public static IQueryable<T> MySearchList(IQueryable<T> myArticleTable, T myArticle)
    {
        //第二步,动态查询我的文章
    
        // List<MyArticle> SearchMyArticleList = new List<MyArticle>();
        //1.我们先定义几个查询的参数(文章标题,浏览数,发布时间)              
    
        ParameterExpression myart = Expression.Parameter(typeof(T), "article");   //标题     
        ParameterExpression searchTitle = Expression.Parameter(typeof(string), "searchTitle");   //标题     
        ParameterExpression searchViews = Expression.Parameter(typeof(int), "searchViews");     //浏览数   
        ParameterExpression searchPublished = Expression.Parameter(typeof(DateTime), "searchPublished");//创建月份
    
        //2.使用表达式树,动态生成查询 (查询某个日期的文章)
        Expression left = Expression.Property(myart, typeof(T).GetProperty("published")); //访问属性的表达式
        Expression right = Expression.Property(Expression.Constant(myArticle), typeof(T).GetProperty("published"));//访问属性的表达式
        Expression e1 = Expression.GreaterThanOrEqual(left, right); //大于等于
    
        //2.使用表达式树,动态生成查询 (按点击数查询)
        Expression left2 = Expression.Property(myart, typeof(T).GetProperty("views")); //访问属性的表达式
        Expression right2 = Expression.Property(Expression.Constant(myArticle), typeof(T).GetProperty("views"));//访问属性的表达式
        Expression e2 = Expression.GreaterThanOrEqual(left2, right2);
    
        //3.构造动态查询 (按点击数和月份查询)
        Expression predicateBody = Expression.AndAlso(e1, e2);
    
        //4.构造过滤
        MethodCallExpression whereCallExpression = Expression.Call(
        typeof(Queryable),
        "Where",
        new Type[] { typeof(T) },
        myArticleTable.Expression,
        Expression.Lambda<Func<T, bool>>(predicateBody, new ParameterExpression[] { myart }));
    
        //构造排序
        MethodCallExpression orderByCallExpression = Expression.Call(
        typeof(Queryable),
        "OrderByDescending",
        new Type[] { typeof(T), typeof(int) },
        whereCallExpression,
        Expression.Lambda<Func<T, int>>(left2, new ParameterExpression[] { myart }));
    
        //创建查询表达式树
        IQueryable<T> results = myArticleTable.Provider.CreateQuery<T>(orderByCallExpression);
    
        return results;
    }

    调用方法

    IQueryable<MyArticle> results = ExpressionTree<MyArticle>.MySearchList(myArticleList.AsQueryable<MyArticle>(), new MyArticle() { views=500, published=Convert.ToDateTime("2015-06")});
                    
    foreach (MyArticle article in results)
        Console.WriteLine(article.title + " 
     [发布日期:"+article.published+"] [浏览数:"+article.views+"]");

    运行结果如下:

    我们查询的是 发布日期在6月1日以后,点击量在500以上的文章

    6.要点:

    本节通过动态创建表达式树、执行表达式树及表达式树的调试的学习,最后通过一个动态查询博客园文章结束,使小伙伴们能熟练认识表达式树在动态查询上带来的便利。

    [花絮]:晚上写博客过程中,我家汪一直抓了我N次,让我时刻保持清醒状态,最终完成本篇,下面是家汪的靓照:

    ==============================================================================================  

     返回目录

     <如果对你有帮助,记得点一下推荐哦,如有有不明白或错误之处,请多交流>  

    <对本系列文章阅读有困难的朋友,请先看《.net 面向对象编程基础》>

    <转载声明:技术需要共享精神,欢迎转载本博客中的文章,但请注明版权及URL>

    .NET 技术交流群:467189533    .NET 程序设计

    ==============================================================================================   

  • 相关阅读:
    树莓派配置Jdk环境并设置开机启动jar
    缓存架构之路(一)缓存概述及应用
    并发编程学习历程(零)JMM内存模型
    并发编程学习历程(一)Synchronized
    深入Disruptor源码分析(一)入门指南
    JAVA Synchronized和Lock实现原理
    Vscode断点调试PHP
    php环境搭建
    github使用总结
    面试笔记
  • 原文地址:https://www.cnblogs.com/yubinfeng/p/4624144.html
Copyright © 2020-2023  润新知