zoukankan      html  css  js  c++  java
  • LINQ to Objects系列(4)表达式树

      为了进一步加深对Lambda表达式的理解,我们需要掌握一个新的知识,Lambda表达式树,可能听名字看起来很高深和难以理解,但实际上理解起来并没有想象中那么难,这篇文章我想分以下几点进行总结。

    1,表达式树的语法

    2,将代码转换到数据

    3,探索表达式树

    4,将数据转换到代码

    5,IQueryable<T>和表达式树

    6,为什么要将LINQ to SQL查询表达式转换成表达式树?

    7,IQueryable<T>和IEnumerable<T>

    8,总结

    表达式树的语法

    //利用Lambda表达式定义一个Func委托
    Func<int, int, int> function = (a, b) => a + b;

    变量function指向两个数字相加的原生可执行的代码,上面这个Lambda表达式等价于下面这个方法。

    public int function(int a, int b)
    {
        return a + b;
    }

    这个Lambda表达式和这个方法都可以这样调用。

    int c = function(1,2); //结果为:3

    将代码转换到数据

    表达式树不是一段可执行代码,而是一种数据结构。那么怎么将Lambda表达式转换成表达式树呢?

    我们可以使用命名空间System.Linq.Expressions下的Expression类来实现这个需求。

    例如我们先创建一个表达式树,如下代码。

    //创建表达式树
    Expression<Func<int, int, int>> expression = (a, b) => a + b;

    这样,我们就创建一个类型为Expression<T>的表达式树,标识expression不是可执行代码;它是一个名叫表达式树的数据结构。我们可以使用工具ExpressionTreeVisualizer来浏览表达式树,如下图。

    ExpressionTree

    探索表达式树

    Expression<TDelegate>类有四个属性:

    • Body: 得到表达式的主体。
    • Parameters: 得到lambda表达式的参数.
    • NodeType: 获取树的节点的ExpressionType。共45种不同值,包含所有表达式节点各种可能的类型,例如返回常量,例如返回参数,例如取两个值的小值(<),例如取两个值的大值(>),例如将值相加(+),等等。
    • Type: 获取表达式的一个静态类型。在这个例子里,表达式的类型是Func<intintint>。

    那么怎么查看表达式树中的参数名称呢?从上图中我们可以看出,参数是一个ReadOnlyCollection集合,所以我们可以通过索引来访问。如下代码。

    //访问表达式树的参数
    Console.WriteLine("参数1:{0},参数2:{1}",expression.Parameters[0],expression.Parameters[1]);

    接下来,怎么查看表达式树的Body体呢?在这个例子里是(a+b)。代码如下。

    //访问表达式树的Body
    BinaryExpression body = expression.Body as BinaryExpression;
    ParameterExpression left = body.Left as ParameterExpression;
    ParameterExpression right = body.Right as ParameterExpression;
    Console.WriteLine(expression.Body);
    Console.WriteLine(" 表达式左边部分: " + "{0}{4} 节点类型: {1}{4} 表达式右边部分: {2}{4} 类型: {3}{4}", left.Name, body.NodeType, right.Name, body.Type, Environment.NewLine);

    输出结果为:

    通过探索表达式树,我们可以分析表达式的各个部分发现它的组成。你可以看见,我们的表达式的所有元素都展示为像节点这样的数据结构。表达式树是代码转换成的数据。

    将数据转换到代码

    我们可以将代码转换为数据,那么我们也可将数据转换为代码。下面的代码说明了如果将数据(表达式树数据结构)转换为代码。

    //将数据(表达式树)转换为代码
    int result = expression.Compile()(1,2);
    Console.WriteLine(result); //输出结果为:3

    可以发现,程序输出结果与Lambda表达式执行结果一样。

     IQueryable<T>和表达式树

    现在至少你有一个抽象的概念理解表达式树,现在是时候回来理解其在LINQ中的关键作用了,尤其是在LINQ to SQL中。花点时间考虑这个标准的LINQ to SQL查询表达式:

    var query = from c in db.Customers 
                where c.City == "Nantes" 
                select new { c.City, c.CompanyName };


    你可能知道,这里LINQ表达式返回的变量query是IQueryable类型。这里是IQueryable类型的定义:

    public interface IQueryable : IEnumerable 
    {
      Type ElementType { get; }
      Expression Expression { get; }
      IQueryProvider Provider { get; }
    }

    你可以看见,IQueryable包含一个类型为Expression的属性,Expression是Expression<T>的基类。IQueryable的实例被设计成拥有一个相关的表达式树。它是一个等同于查询表达式中的可执行代码的数据结构。

     为什么要将LINQ to SQL查询表达式转换成表达式树?

    现在我们知道,表达式树是一个用来表示可执行代码的数据结构。那我们为什么要将LINQ to SQL查询表达式转换成表达式树呢?

    一个LINQ to SQL查询不是在C#程序里执行的,而是被转换成SQL语句,通过网络发送,最后在数据库服务器上执行的。也就是说,下面这个LINQ查询不是在C#程序里执行的。

    var query = from c in db.Customers
    
                where c.City == "Nantes"
    
                select new { c.City, c.CompanyName };

    它是被转换成SQL语句后在数据库服务器上运行的。转换后的SQL语句如下代码。

    SELECT [t0].[City], [t0].[CompanyName]
    FROM [dbo].[Customers] AS [t0] 
    WHERE [t0].[City] = @p0

    现在也许可以回答上面的问题。可以用一句话总结:表达式树是为了更方面地将查询表达式转换成字符串(这里指的是SQL语句)并交给其它程序(这里一般指数据库服务器)执行。

    IQueryable<T>和IEnumerable<T>

     我们知道,LINQ to Objects通常返回IEnumerable<T>,而LINQ to SQL返回的是IQueryable<T>。那为什么它们返回的类型会不一样呢?

    我们先来看它们的定义,也许我们可以从它们的定义中找到问题的答案。

    IEnumberable<T>的定义如下:

    public interface IEnumerable<T> : IEnumerable 
    { 
       IEnumerator<T> GetEnumerator();
    }

    IQueryable<T>的定义如下:

    public interface IQueryable : IEnumerable 
    {
      Type ElementType { get; }
      Expression Expression { get; } //表达式树
      IQueryProvider Provider { get; }
    }

    可以看出,IQueryable<T>包含一个Expression表达式树的定义而IEnumberable<T>却没有,这同时也揭示了一个现象,表达式树通常用在LINQ to SQL查询中,而LINQ to Objects中却很少使用。

    那为什么LINQ to Objects中很少使用表达式树呢?是因为LINQ to Objects查询通常在.net程序中就可以完成,不需要将其转换成字符串(或SQL语句)发送到其它程序中执行。

    那么针对这两种返回类型,我们该怎么选择呢?这里有两条原则可以参考:

    • 如果查询表达式可以在本程序里执行的,那么使用Enumberable<T>就可以完成任务。
    • 如果查询表达式需要被转换成字符串并发送到其它程序中执行的,那么就应该使用IQueryable<T>和表达式树。

    总结

    通过以上内容的学习,发现表达式树并没有想象中那么难以理解,关于表达式树我想用以一几句通俗易懂的话总结。

    1. 表达式树是一种用来表示可执行代码(一般指Lambda查询表达式)的树形数据结构。
    2. 表达式树在LINQ to SQL中使用得非常多(因为LINQ to SQL查询返回IQueryable<T>类型),通过表达式树,LINQ to SQL查询表达式更方便地被解析成SQL语句并发送到数据库服务器上执行。
    3. 一条最佳实践原则:LINQ查询如果在程序内执行的不需要表达式树,当代码在程序外部执行时则需要使用表达式树。
  • 相关阅读:
    MFC tab页面中获到其它页面的数据
    sqlite数据库中"Select * From XXX能查到数据,但是Select DISTINCT group From xxx Order By group却查不出来
    关闭程序出现崩溃(exe 已触发了一个断点及未加载ucrtbased.pdb)
    springboot 通用Mapper使用
    springBoot 发布war包
    springCloud Zuul网关
    springboot hystrix turbine 聚合监控
    springBoot Feign Hystrix Dashboard
    springBoot Ribbon Hystrix Dashboard
    springBoot Feign Hystrix
  • 原文地址:https://www.cnblogs.com/mcgrady/p/3732694.html
Copyright © 2011-2022 走看看