表达式树总结

基础

表达式树提供了一个将可执行代码转换成数据的方法.如果你要在执行代码之前修改或转换此代码,那么它是很有用的.有其是当你要将C#代码----如LINQ查询表达式转换成其他代码在另一个程序----如SQL数据库里操作它.

表达式树的语法:

考虑下面简单的Lambda表达式:

Func<int,int,int>function=(a,b)=>a+b;

这个语法包含三个部分:

1.一个声明 : Func<int,int,int>function

2.一个等号 : =

3.一个Lambda表达式 : (a,b)=>a+b

变量function指向两个数字相加的原声可执行代码.上面散的Lambda表达式表示一个简短的如下的手写方法:

public int function(int a,int b)

{

return a+b;

}

上面的方法或lambda表达式都可以这样调用:

int c=function(3,4);

当上面的方法调用后,变量c将被设成3+4,变成7

上面声明中第一步委托类型Func是在System命名空间中为我们定义好的:

public delegate TResult FUnc<T1,T2,TResult>(T1 arg1,T2 arg2);

这个代码看上去很复杂,但他在这里只是用来帮助我们定义变量function,变量function赋值为非常简单的两个数字相加的Lambda表达式.及时你不懂委托和泛型,仍然应该清楚这是一个声明可执行代码变量引用的方法.在这个例子里它指向一个非常简单的可执行代码.

将代码转换到数据中

我们已经看到怎么声明一个指向原声可执行代码的变量.表达式树不是可执行代码,他是一种数据结构,那么我们怎从表达式的原声带吗转换成表达式树?怎么从代码转换成数据?

LINQ提供一个简单的用法将代码转换到名叫表达式树的数据结构.首先添加命名空间:Linq.Expression

现在我们创建一个表达式树

Expression<Func<int,int,int>>expression=(a,b)=>a+b;

和上例一样的Lambda表达式用来转换到类型为Expression<T>的表达式树.标示expression不是可执行代码了他还是一个名叫表达式树的数据结构.

Visual Studio 2008的samples包含一个叫ExpressionTreeVisualizer的程序。它可以用来呈现表达式树。图1你可以看到一个展示上面简单表达式语句的对话框截图。注意,对话框上面部分显示的是lambda表达式,下面是用TreeView控件显示的其组成部分

编写代码来探索表达式树

我们的例子是一个Expression<TDelegate>.Exoression<TDelegate>类有四个属性:

Body:得到表达式的主体

Parameters:得到lambda 表达式的参数

NodeType:获取树的节点的ExpressionType.共45种不同的值,包含所有表达式节点各种可能的类型,例如返货常量,例如返回参数,例如取两个值的小值(<),例如取两个值的大值(>),例如将值相加(+),等等.

Type:获取表达式的一个静态类型.在这个例子里,表达式的类型是Func<int,int,int>.

如果我们折叠图1的节点,Expression<TDelegate>的四个属性便显示的很清楚:

图2:将树节点折叠起来,你可以很容易的看到Expression<TDelegate>类的四个主要属性.

你可以使用这四个属性开始探索表达式树.例如,你可以通过这样找到参数的名称:

Console.WriteLine(“参数1 : {0} , 参数2 : {1}”,expression.Parameters[0],expression.Parameters[1]);

这句代码取出值a和b:

参数1: a ,参数2: b

这个很容易在图1的ParameterExpression节点找到

让我们在接下来的代码探索表达式的Body,在这个例子里是(a+b):

BinaryExpression body = (BinaryExpression)expression.Body;

ParameterExpression left = (ParameterExpression)body.Left;

ParameterExpression right = (ParameterExpression)body.Right;

Console.WriteLine(expression.Body);

Console.WriteLine(" 表达式左边部分: " + "{0}{4} 节点类型: {1}{4} 表达式右边部分: {2}{4} 类型: {3}{4}", left.Name, body.NodeType, right.Name, body.Type, Environment.NewLine);

这段代码产生如下输入:

(a + b)
  表达式左边部分: a
  节点类型:  Add
  表达式右边部分: b
  类型: System.Int32

同样,你会发现很容易在图1的Body节点中找到这些信息。

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

编译一个表达式:将数据转换会代码

如果我们可以将代码转换到数据,那么我们也应该能将数据转换会代码.这里是让编译器将表达式树转换到可执行代码的简单代码.

int result=expression.Compile()(3,5);

Console.WriteLine(result);

这段代码会输出值8,跟本文最初生命的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是IQuerable类型,这里是IQueryable类型的定义:

public interface IQueryable:IEnumerable

{

Type ElementType{get;}

Expression Expression{get;}

IQueryProvider Provider{get;}

}

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

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

表达式树是一个用来表示可执行代码的数据结构.但到目前为止我们仍然存在一个核心问题,那就是我们为什么要这么做?

一个LINQ to SQL查询不是在你的C#程序里执行的.相反,他被转换成SQL,通过网络发送,最后在数据库服务器上执行.换句话说,下面的代码实际上从来不会在你的程序里执行:

var query=from c in db.Customers

where c.City==”BeiJing”

select new {c.City,c.CompantName};

他首先被转换成下面的SQL语句然后在服务器上执行:

SELECT [t0].[City], [t0].[CompanyName]

FROM [dbo].[Customers] AS [t0]

WHERE [t0].[City] = @p0

从查询表达式的代码转换成SQL查询语句----他可以通过字符串形式被发送到其他程序.在这里,这个程序恰好是SQL Server数据库.像这样将数据结构转换到SQL显然比直接从原声IL或可执行代码转换到SQL要容易的多.这有些夸大问题的难度,只要事项转换0和1的序列到SQL.

现在是时候将你的查询表达式转换成SQL,描述查询的表达式树是分解并解析了的,就像分解简单的Lambda表达式树一样.当然,解析LINQ to SQL表达式树的算法很复杂,但规则是一样的,一旦了解了表达式树的各部分,那么LINQ开始斟酌以最好的方式生成返回被请求的数据的SQL语句.

表达式树被创建是为了制造一个像查询表达式转换成字符串以传递给其他程序并在那里执行这样的转换任务,就是这么简单.没有什么特别的.只是简单的:把代码,转换成数据,然后分析数据发现其组成部分,最后转换成可以传递到其他程序的字符串.

于查询来自编译器封装的抽象的数据结构,编译器可以获取任何它想要的信息。它不要求执行查询要在特定的顺序,或用特定的方式。相反,它可以分析表达式树,寻找你要做的是什么,然后再决定怎么去做。至少在理论上,我们可以自由的考虑各种因素,比如网络状况,数据库负载,结果集是否有效,等等。在实际中LINQ to SQL不考虑所有这些因素,但它理论上可以自由的做几乎所有想做的事。此外,人们可以通过表达式树将自己编写的代码,分析并转换成跟LINQ to SQL提供的完全不同的东西。

IQueryable<T>和IEnumerable<T>

正如你知道的,LINQ to Objects的查询表达式返回IEnumerable<T>而不是IQueryable<T>.为什么LINQ to Objects使用IEnumerable<T>而LINQ to SQL使用IQueryable<T>?

这里是IEnumerable<T>的定义:

public interface IEnumerable<T> : IEnumerable

{

IEnumerator<T> GetEnumerator();

}

正如你看到的,IEnumerable<T>并不包含类型为Expression的属性。这指出LINQ to Objects和LINQ to SQL的根本区别。后者大量使用了表达式树,但LINQ to Objects很少使用。

为什么表达式树不是LINQ to Objects的标准部分?虽然答案不一定会马上出现,但这是很有意义的一旦你发现这个问题。

考虑这个简单LINQ to Objects查询表达式:

List<int> list = new List<int>() { 1, 2, 3 };

var query = from number in list

where number < 3

select number;

这个LINQ查询返回在我们的list中比3小的数字;就是说,这里返回数字1和2。显然没有必要将查询转换成字符串来顺序传递给其他程序并获取正确的结果。相反,可以直接转换查询表达式为可执行的.NET代码。这里并不需要将它转换成字符串或对它执行任何其他复杂操作。

可是这有点理论化,在实际中某些特殊情况下其分隔线可能有些模糊,总体上讲规则相当简单:

如果代码可以在程序里执行那么可以使用名为IEnumerable<T>的简单类型完成任务

如果你需要将查询表达式转换成将传递到其他程序的字符串,那么应该使用IQueryable<T>和表达式树。

LINQ to Amazon这样的项目需要将查询表达式转换成web service调用执行外部程序,通常使用IQueryable<T>和表达式树。LINQ to Amazon将它的查询表达式转换成数据,通过web service传递给另一个甚至不是C#写的程序。将C#代码转换成到某些能传递到web service的东西时,表达式树内在的抽象是非常有用的。要在程序内执行的代码,仍然可以经常使用而抛开表达式树。例如下面的查询使用IEnumerable<T>,因为它调用到当前程序的.NET反射API:

var query = from method in typeof(System.Linq.Enumerable).GetMethods()

orderby method.Name

group method by method.Name into g

select new { Name = g.Key, Overloads = g.Count() };

概要:

本文覆盖了表达式树的一些基本情况。通过将代码转换成数据,这些数据结构揭示并描绘表达式的组成部分。从最小的概念上讲,理解表达式树是相当简单的。它获取可执行表达式并获取其组成部分放入树形数据结构。例如,我们检测这个简单的表达式:

(a,b) => a + b;

通过研究来源于这个表达式的树,你能看到创建树的基本规则,见图1。

你同样可以看到表达式树在LINQ to SQL里扮演非常重要的角色。尤其,他们是LINQ to SQL查询表达式用来获取逻辑的数据抽象。解析并分析此数据得到SQL语句,然后发送到服务器。

LINQ使查询C#语言的一个普通类即有类型检查也有智能感知。其代码是类型检查和智能感知的,必须使用正确的C#语法,它能直接转换到可执行代码,就像任何其他C#代码一样被转换和执行。表达式树使将可执行代码转换成能传递到服务器的SQL语句相对容易。

查询返回IEnumerable<T>优于IQueryable<T>表示不使用表达式树。作为一般性规则,可以这么说:LINQ查询在程序内执行时不需要表达式树,当代码在程序外执行时可以利用表达式树。

扩展:Expression Tree Visualizer是一个集成子啊VS中的工具,用于在运行时以树状结构显示出指定的Expression

使用方法:

下载安装包:http://www.fishlee.net/service/download/589

解压后根据你的VS版本选择合适目录下的  ExpressionTreeVisualizer.dll 文件,复制到你的VS安装目录下的Common7\Packages\Debugger\Visualizers  目录里

重启VS

注意,只有在Debug的时候才能看到,

Expression表达式树的实例

表达式树表示树状数据结构的代码,树状结构中的每个节点都是一个表达式,例如一个方法调用或类似x<y的二元运算

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;

namespace 表达式树
{
    class Program
    {
        static void Main(string[] args)
        {
            //利用Lambda表达式创建表达式树
            Expression<Func<int, int, int, int>> expr = (x, y, z) => (x + y) / z;

//编译表达式树,该方法将表达式树表示的代码编译成一个可执行委托
            int res = expr.Compile()(1, 2, 3);

Console.WriteLine("利用Lambda表达式创建表达式树 : " + res);

//使用LambdaExpression构建可执行的代码
            Func<int, int, int, int> fun = (x, y, z) => (x + y) / z;
            Console.WriteLine("使用LambdaExpression构建可执行的代码 : " + fun(1, 2, 3));

//动态构建表达式树
            ParameterExpression pe1 = Expression.Parameter(typeof(int), "x");
            ParameterExpression pe2 = Expression.Parameter(typeof(int), "y");
            ParameterExpression pe3 = Expression.Parameter(typeof(int), "z");
            var body = Expression.Divide(Expression.Add(pe1, pe2), pe3);
            var w = Expression.Lambda<Func<int, int, int, int>>(body, new ParameterExpression[]
                {
                    pe1,pe2,pe3
                });

Console.WriteLine("动态构建表达式树 ; " + w.Compile()(1, 2, 3));
            
            
            List<Entity> list = new List<Entity> { new Entity { Id1 = 1 }, new Entity { Id1 = 2 }, new Entity { Id1 = 3 } };
            //IQueryable<T>的扩展方法,WhereIn的实现
            var d = list.AsQueryable().WhereIn(o => o.Id1, new int[] { 1, 2 });
            d.ToList().ForEach(o =>
            {
                Console.WriteLine(o.Id1);
            });
            Console.ReadKey();
        }
    }
    public class Entity
    {
        public Object Id;
        public int Id1;
        public string Name { set; get; }

}
    public static class CC
    {
        public static IQueryable<T> WhereIn<T, TValue>(this IQueryable<T> query, Expression<Func<T, TValue>> obj, IEnumerable<TValue> values)
        {
            return query.Where(BuildContainsExpression(obj, values));
        }
        private static Expression<Func<TElement, bool>> BuildContainsExpression<TElement, TValue>(Expression<Func<TElement, TValue>> valueSelector, IEnumerable<TValue> values)
        {
            if (null == valueSelector)
            {
                throw new ArgumentNullException("valueSelector");
            }
            if (null == values)
            {
                throw new ArgumentNullException("values");
            }
            var p = valueSelector.Parameters.Single();
            if (!values.Any()) return e => false;
            var equals = values.Select(value => (Expression)Expression.Equal(valueSelector.Body, Expression.Constant(value, typeof(TValue))));
            var body = equals.Aggregate(Expression.Or);
            return Expression.Lambda<Func<TElement, bool>>(body, p);
        }
    }
}

C#编程(六十六)----------表达式树总结的更多相关文章

  1. C#高级编程六十六天----表达式树总结【转】

    https://blog.csdn.net/shanyongxu/article/details/47257139 表达式树总结 基础 表达式树提供了一个将可执行代码转换成数据的方法.如果你要在执行代 ...

  2. 第三百六十六节,Python分布式爬虫打造搜索引擎Scrapy精讲—elasticsearch(搜索引擎)的bool组合查询

    第三百六十六节,Python分布式爬虫打造搜索引擎Scrapy精讲—elasticsearch(搜索引擎)的bool组合查询 bool查询说明 filter:[],字段的过滤,不参与打分must:[] ...

  3. “全栈2019”Java第六十六章:抽象类与接口详细对比

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...

  4. 《手把手教你》系列技巧篇(六十六)-java+ selenium自动化测试 - 读写excel文件 - 上篇(详细教程)

    1.简介 在自动化测试,有些我们的测试数据是放到excel文件中,尤其是在做数据驱动测试的时候,所以需要懂得如何操作获取excel内的内容.由于java不像python那样有直接操作Excle文件的类 ...

  5. 学习ASP.NET Core Razor 编程系列十六——排序

    学习ASP.NET Core Razor 编程系列目录 学习ASP.NET Core Razor 编程系列一 学习ASP.NET Core Razor 编程系列二——添加一个实体 学习ASP.NET ...

  6. C#编程(十六)----------匿名类型

    匿名类型 var和new关键字一起使用,可以创建匿名类型. 匿名类型提供了一种方便的方法,可用来将一组只读属性封装到单个对象中,而无需首先显式定义一个类型. 类型名由编译器生成,并且不能在源代码级使用 ...

  7. java并发编程(十六)happen-before规则

    转载请注明出处:http://blog.csdn.net/ns_code/article/details/17348313 happen-before规则介绍 Java语言中有一个"先行发生 ...

  8. 并发编程(十六)——java7 深入并发包 ConcurrentHashMap 源码解析

    以前写过介绍HashMap的文章,文中提到过HashMap在put的时候,插入的元素超过了容量(由负载因子决定)的范围就会触发扩容操作,就是rehash,这个会重新将原数组的内容重新hash到新的扩容 ...

  9. java并发编程(十六)----(线程池)java线程池的使用

    上节我们简单介绍了线程池,这次我们就来使用一下.Executors提供四种线程池,分别是:newCachedThreadPool,newFixedThreadPool ,newScheduledThr ...

随机推荐

  1. thymeleaf:访问静态方法

    <p class="left tel" th:if="${#strings.startsWith(T(net.common.util.tool.common.Req ...

  2. html可用于跨域的三个标签

    img 应用场景: <img>用于打点统计,统计网站可能是其他域 script 应用场景: 可以用于cdn,还可以用于JSONP link 可用于cdn

  3. ***文件上传控件bootstrap-fileinput的使用和参数配置说明

    特别注意:    引入所需文件后页面刷新查看样式奇怪,浏览器提示错误等,可能是因为js.css文件的引用顺序问题,zh.js需要在fileinput.js后面引入.bootstrap最好在filein ...

  4. MAC下代理工具Charles使用

    一.跟踪HTTPS 1.下载官方的证书ssl.zip证书,解压成*.crt 2.可以通过邮箱或者发布到自己的服务器的方式,然后用手机去下载安装crt文件. 3.charles设置Proxy--> ...

  5. hdu 1232 变成生成树至少还要加几条边 (并查集模板题)

    求一个图 变成生成树至少还要加几条边(成环的边要删掉,但不用统计) Sample Input4 2 //n m1 3//u v4 33 31 21 32 35 21 23 5999 00 Sample ...

  6. SprintBoot 1.2.8 入门

    现在SpringBoot官网Quick Start的版本是1.5.3,试了一下,报错说我JDK版本太低,查了一下说是需要JDK8,所以我使用了旧版本1.2.8,实际上在POM中的依赖配置方式一样的. ...

  7. word2vec 中的数学原理二 预备知识 霍夫曼树

    主要参考:    word2vec 中的数学原理详解                 自己动手写 word2vec 编码的话,根是不记录在编码中的 这一篇主要讲的就是霍夫曼树(最优二叉树)和编码.  ...

  8. 【LOJ】#2567. 「APIO2016」划艇

    题解 显然有个很暴力的dp,\(dp[i][j]\)表示选到第\(i\)个数,末尾的数是\(j\)的方案数 但是第二维就开不下了,怎么办呢 考虑离散化整个区间,我们记录\(dp[i][j][k]\)表 ...

  9. HTML、CSS、JS 样式 (未整理)

    随手记,有错误的地方希望留言 ^.-.^ PHP 实现关闭浏览器窗口echo "<script>window.close();</script>"; jqu ...

  10. Noip2018游记——AFO

    本来Day 0和Day 1写得挺轻松的,结果没想到Day 2是这样的画风...心情逐渐沉重... Day 0 白天的时候颓的一批,上午考的信心赛还打错了一个字母然后$100pts\rightarrow ...