https://blog.csdn.net/shanyongxu/article/details/47257139

表达式树总结

基础

表达式树提供了一个将可执行代码转换成数据的方法.如果你要在执行代码之前修改或转换此代码,那么它是很有用的.有其是当你要将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#高级编程六十九天----DLR简介 .在.NET中使用DLR(转载) 我也来说说Dynamic

    DLR 一.近年来,在TIOBE公司每个月发布的编程语言排行榜中,C#总是能挤进前十名,而在最近十年来,C#总体上呈现上升的趋势.C#能取得这样的成绩,有很多因素,其中它在语言特性上的锐意进取让人印象 ...

  2. 孤荷凌寒自学python第六十六天学习mongoDB的基本操作并进行简单封装5

    孤荷凌寒自学python第六十六天学习mongoDB的基本操作并进行简单封装5并学习权限设置 (完整学习过程屏幕记录视频地址在文末) 今天是学习mongoDB数据库的第十二天. 今天继续学习mongo ...

  3. C++面向对象高级编程(六)转换函数与non-explicit one argument ctor

    技术在于交流.沟通,转载请注明出处并保持作品的完整性. 1.conversion function 转换函数 //1.转换函数 //conversion function //只要你认为合理 你可以任 ...

  4. 第六十六天 js操作高级

    1.对象使用的高级 对象的key为字符类型,value为任意类型 var obj ={ name:"name", "person-age":18 } // 访问 ...

  5. 【读书笔记】C#高级编程 第十六章 错误和异常

    (一)简介 错误的出现并不总是编写应用程序的人的原因,有时应用程序会因为应用程序的最终用户引发或运行代码的环境而发生错误.C#提供了异常处理机制来处理错误. (二)异常类 在C#中,但刚出现某个特殊的 ...

  6. Python - Socket网络编程 - 第二十六天

    网络编程 Python 提供了两个级别访问的网络服务.: 低级别的网络服务支持基本的 Socket,它提供了标准的 BSD Sockets API,可以访问底层操作系统Socket接口的全部方法. 高 ...

  7. 【读书笔记】C#高级编程 第十九章 程序集

    (一)程序集的含义 程序集是.NET用于部署和配置单元的术语. .NET应用程序包含一个或多个程序集.通常扩展名是EXE或DLL的.NET可执行程序称为程序集. 程序集是自我描述的安装单元,由一个或多 ...

  8. C#高级编程五十四天----Lookup类和有序字典

    Lookup类 Dictionary<Tkey,TValue>仅仅为每一个键支持一个值.新类Lookup<Tkey,TValue>是.NET3.5中新增的,它类似与Dictio ...

  9. 【读书笔记】C#高级编程 第二十四章 文件和注册表操作

    (一)文件和注册表 对于文件系统操作,相关的类几乎都在System.IO名称空间中,而注册表操作由System.Win32名称空间中的类来处理. (二)管理文件系统 System.MarshalByR ...

随机推荐

  1. ServletContextListener作用

    ServletContext 被 Servlet 程序用来与 Web 容器通信.例如写日志,转发请求.每一个 Web 应用程序含有一个Context,被Web应用内的各个程序共享.因为Context可 ...

  2. Android通知栏沉浸式/透明化完整解决方案

    转载请注明出处:http://www.cnblogs.com/cnwutianhao/p/6640649.html 参考文献:https://github.com/ljgsonx/adaptiveSt ...

  3. Linux学习笔记05—文件与目录权限

    1. 绝对路径与相对路径绝对路径:路径的写法一定由根目录 ‘/’写起,例如 /usr/local/mysql 这就是绝对路径相对路径:路径的写法不是由根目录 ‘/’写起,例如:首先用户进入到/, 然后 ...

  4. CAT架构的应用与实践 IT大咖说 - 大咖干货,不再错过

    CAT架构的应用与实践 IT大咖说 - 大咖干货,不再错过 http://www.itdks.com/dakashuo/new/dakalive/detail/594

  5. 【汇总】PHP-FPM 配置优化(转)

    -----------------------开启php-fpm慢脚本日志 request_slowlog_timeout = 30sslowlog = /usr/local/php/var/log/ ...

  6. Apache Kafka —一个不同的消息系统

    Apache已经发布了Kafka 0.8,也是自从成为Apache软件基金会的顶级项目后Kafka的 第一个主版本. Apache Kafka是发布—订阅消息传递,实现了分布式提交日志,适用于离线和在 ...

  7. WebLogic使用总结(二)——WebLogic卸载

    一.WebLogic 12c的卸载 WebLogic的卸载是非常容易的,找到WebLogic的卸载程序,如下图所示: 启动卸载程序,如下图所示:

  8. 基于设备树的TQ2440的中断(2)

    下面以按键中断为例看看基于设备数的中断的用法: 设备树: tq2440_key { compatible = "tq2440,key"; interrupt-parent = &l ...

  9. eclipse使用profile完成不同环境的maven打包功能

    原文:https://blog.csdn.net/duan9421/article/details/79086335 我们在日常开发工作中通常会根据不同的项目运行环境,添加不同的配置文件,例如 开发环 ...

  10. Android 中的概念大集合

    Intent: An Intent is an object that provides runtime binding between separate components (such as tw ...