C#特性-表达式树

 

表达式树ExpressionTree

 

表达式树基础

转载需注明出处:http://www.cnblogs.com/tianfan/

 

 

刚接触LINQ的人往往觉得表达式树很不容易理解。通过这篇文章我希望大家看到它其实并不像想象中那么难。您只要有普通的LINQ知识便可以轻松理解本文。

 

表达式树提供一个将可执行代码转换成数据的方法。如果你要在执行代码之前修改或转换此代码,那么它是非常有价值的。尤其是当你要将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, 5);


当方法调用后,变量c将被设成3+5,即8。

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

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

 

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

 

将代码转换到数据中

 

在上一节,你看到怎么声明一个指向原生可执行代码的变量。表达式树不是可执行代码,它是一种数据结构。那么我们怎么从表达式的原生代码转换成表达式树?怎么从代码转换成数据?

 

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

 

using System.Linq.Expressions;


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

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

 

跟上个例子一样的lambda表达式用来转换到类型为Expression<T>的表达式树。标识expression不是可执行代码;它是一个名叫表达式树的数据结构。

 

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

ExpressionTree


图1:VS2008 C# Samples中的ExpressionTreeVisualizer创建一个表达式树的象征性的输出

编写代码来探索表达式树

 

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

 

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

 

ExpressionTreeProperties

 

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

 

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

 

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

 

这句代码输出值ab

 

参数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是IQueryable类型。这里是IQueryable类型的定义:

 

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

 

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

 

花点时间考虑图3。你可能需要点击它使图片原尺寸显示。这是本节开始的查询表达式的表达式树的可视化显示。此图使用ExpressionTreeVisualizer创建,就像我使用它在图1创建基础的lambda表达式树一样。

 

LinqToSqlExpressionTree01

 

图3:此复杂的表达式树由上面的样例LINQ to SQL查询表达式生成。(点击图片看查看大图)

 

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

 

你已经学习了表达式树是一个用来表示可执行代码的数据结构。但到目前为止我们还没有回答一个核心问题,那就是为什么我们要做这样的转换。这个问题是我们在本文开始时提出来的,现在是时候回答了。

 

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

 

var query = from c in db.Customers

            where c.City == "Nantes"

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

 

它首先被转换成下面的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查询在程序内执行时不需要表达式树,当代码在程序外执行时可以利用表达式树。

 

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

已经有很多很多人聊过这个话题,今天我在这里重复也不会探讨出什么新东西,只是把自己的理解描述出来,更是为了整个系列文章的完整性。

当你听说Linq给你的承诺时,你怎么想的?Wow,我们可以以统一的方式操作各种各样的数据了。这就是我当时的想法。虽然人们在现实中总是喜欢差异,认为差异才能产生美,如果一切的一切都是一样的,这个世界将无比的单调,可是作为程序员的我们却对标准趋之若鹜,对差异嫉恶如仇。看同桌的你是不是正在为了Oracle和Sql Server两种数据库编写两套数据访问的类?

表达式树概念

Linq的承诺貌似Java那个梦想一样:Write Once,Run Anywhere。Java是怎么做到的?Sun等公司为我们在各种平台架构上实现了各自的虚拟机,Java的编译分为两个阶段,第一阶段将Java代码编译为字节码,在这个阶段不管在什么平台上,只要Java源代码一样生成的字节码是一致的,第二个阶段,也就是运行阶段,虚拟机会根据平台的不同生成不同的代码。就是通过将编译器分为前端和后端来实现这个梦想。

实际上LINQ也是这么做的,对各种数据的操作无非“增删查改”,但是具体做的时候关系数据库需要使用SQL来操作,而XML需要XPath来操作。我们如何将“增删查改”的语法做到一致,让我们用起来好像操作的数据只有一种?

答案是使用表达式树

表达式树仅仅是将表达式(这里特指Lambda表达式)用树状的数据结构来表示。相当于Java的字节码,至于如何去解析这个树的结构那就看你自己了,如何去解释这个表达式树。

看下面这个Lambda表达式:

username => username == “yuyi”

我们主要看表达式的主体:username == “yuyi”,如果我们是要对数据库进行操作,这将翻译为字段username中所有值为”yuyi”的行,如果操作的是XML那也许是查询名称为username,值为”yuyi”的attribute。表达式树承载的只是这样一个结构:  

如何解释它这是你的事。

在C#中Expression<Function<string,bool>> IsTrue = username=>username==”yuyi”,就表示一个表达式树,这个语句在编译后就组成为一个树状的结构。

在编译原理中,我们知道编译器的前两个阶段主要做的是:词法分析、语法分析。在词法分析中编译器会从代码文件中读入一个个的字符,然后识别出其中的关键字、标识符、运算符、常量、字符串等。

比如上面的表达式就会识别出:

Username->标识符

==->运算符

“yuyi”->字符串

这些东西就叫做符号

然后以这些符号作为输入进行语法分析,语法分析会将这些符号组合成一个树,这个树我们称之为抽象语法树(AST),表达式树也是一种简单的AST

(在VS2008带的例子中有个DynamicQuery的例子,这里有一个Dynamic.cs文件,这里就自己做表达式的解析,只不过解析的是用字符串形式的表达式,通过这个代码你可以看看表达式的解析过程,这里有一个ExpressionParser类,它就是负责表达式的词法解析的,该类里有一个Token的机构,这就是上面所说的符号,TokenId表示符号的类型

<"Samples"1033"CSharpSamples"LinqSamples"DynamicQuery>

对于Lambda表达式,有两种编译方式,常规的:

Func<string,bool> IsTrue = username => username == “yuyi”;

这个时候编译器会将其编译为匿名方法,关于匿名方法的相关介绍可以参看我这篇文章。

编译成表达式树:

Expression<Func<string,bool>> IsTrue = username => username == “yuyi”;

这么细微的差别,C#这次却不真的编译这条语句,而是将其进行词法、语法分析,得到的是一个数据结构。

我们注意到表达式树还有一个Compile实例方法,它可以将表达式编译成匿名方法,也就是这个数据结构可以向匿名方法转变。

想想Linq to SQL,实际上它不就是C#作为源语言,SQL作为目标语言的一个编译过程么。形成表达式树(抽象语法树)后,就是代码生成了,只不过这个代码生成有的时候是生成代码,比如Linq to SQL,生成的是SQL语句,有的时候是生成的方法调用,比如Linq to XML,生成的是对XML文档的操作。

为什么一样的语句,有的时候是操作内存中的对象集合,有的时候是操作远程数据库?请查看我这篇文章。

关于表达式树更多细节内容,你可以查看TerryLee老大的这篇图文并茂的文章

已经有很多很多人聊过这个话题,今天我在这里重复也不会探讨出什么新东西,只是把自己的理解描述出来,更是为了整个系列文章的完整性。

当你听说Linq给你的承诺时,你怎么想的?Wow,我们可以以统一的方式操作各种各样的数据了。这就是我当时的想法。虽然人们在现实中总是喜欢差异,认为差异才能产生美,如果一切的一切都是一样的,这个世界将无比的单调,可是作为程序员的我们却对标准趋之若鹜,对差异嫉恶如仇。看同桌的你是不是正在为了Oracle和Sql Server两种数据库编写两套数据访问的类?

表达式树概念

Linq的承诺貌似Java那个梦想一样:Write Once,Run Anywhere。Java是怎么做到的?Sun等公司为我们在各种平台架构上实现了各自的虚拟机,Java的编译分为两个阶段,第一阶段将Java代码编译为字节码,在这个阶段不管在什么平台上,只要Java源代码一样生成的字节码是一致的,第二个阶段,也就是运行阶段,虚拟机会根据平台的不同生成不同的代码。就是通过将编译器分为前端和后端来实现这个梦想。

实际上LINQ也是这么做的,对各种数据的操作无非“增删查改”,但是具体做的时候关系数据库需要使用SQL来操作,而XML需要XPath来操作。我们如何将“增删查改”的语法做到一致,让我们用起来好像操作的数据只有一种?

答案是使用表达式树

表达式树仅仅是将表达式(这里特指Lambda表达式)用树状的数据结构来表示。相当于Java的字节码,至于如何去解析这个树的结构那就看你自己了,如何去解释这个表达式树。

看下面这个Lambda表达式:

username => username == “yuyi”

我们主要看表达式的主体:username == “yuyi”,如果我们是要对数据库进行操作,这将翻译为字段username中所有值为”yuyi”的行,如果操作的是XML那也许是查询名称为username,值为”yuyi”的attribute。表达式树承载的只是这样一个结构:  

如何解释它这是你的事。

在C#中Expression<Function<string,bool>> IsTrue = username=>username==”yuyi”,就表示一个表达式树,这个语句在编译后就组成为一个树状的结构。

在编译原理中,我们知道编译器的前两个阶段主要做的是:词法分析、语法分析。在词法分析中编译器会从代码文件中读入一个个的字符,然后识别出其中的关键字、标识符、运算符、常量、字符串等。

比如上面的表达式就会识别出:

Username->标识符

==->运算符

“yuyi”->字符串

这些东西就叫做符号

然后以这些符号作为输入进行语法分析,语法分析会将这些符号组合成一个树,这个树我们称之为抽象语法树(AST),表达式树也是一种简单的AST

(在VS2008带的例子中有个DynamicQuery的例子,这里有一个Dynamic.cs文件,这里就自己做表达式的解析,只不过解析的是用字符串形式的表达式,通过这个代码你可以看看表达式的解析过程,这里有一个ExpressionParser类,它就是负责表达式的词法解析的,该类里有一个Token的机构,这就是上面所说的符号,TokenId表示符号的类型

<"Samples"1033"CSharpSamples"LinqSamples"DynamicQuery>

对于Lambda表达式,有两种编译方式,常规的:

Func<string,bool> IsTrue = username => username == “yuyi”;

这个时候编译器会将其编译为匿名方法,关于匿名方法的相关介绍可以参看我这篇文章。

编译成表达式树:

Expression<Func<string,bool>> IsTrue = username => username == “yuyi”;

这么细微的差别,C#这次却不真的编译这条语句,而是将其进行词法、语法分析,得到的是一个数据结构。

我们注意到表达式树还有一个Compile实例方法,它可以将表达式编译成匿名方法,也就是这个数据结构可以向匿名方法转变。

想想Linq to SQL,实际上它不就是C#作为源语言,SQL作为目标语言的一个编译过程么。形成表达式树(抽象语法树)后,就是代码生成了,只不过这个代码生成有的时候是生成代码,比如Linq to SQL,生成的是SQL语句,有的时候是生成的方法调用,比如Linq to XML,生成的是对XML文档的操作。

为什么一样的语句,有的时候是操作内存中的对象集合,有的时候是操作远程数据库?请查看我这篇文章。

关于表达式树更多细节内容,你可以查看TerryLee老大的这篇图文并茂的文章

 

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

C#中的表达式树

    本人之前从未接触过表达式树的概念,所以特意从网上找到两篇这方面的资料学习了下。本文为阅读笔记性质博客!

    表达式树是.NET 3.5之后引入的,它是一个强大灵活的工具(比如用在LINQ中构造动态查询)。

    先来看看Expression类的API接口:

复制代码
using System.Collections.ObjectModel;namespace System.Linq.Expressions
{// Summary: // Represents a strongly typed lambda expression as a data structure in the // form of an expression tree. This class cannot be inherited. // // Type parameters: // TDelegate: // The type of the delegate that the System.Linq.Expressions.Expression<tdelegate> // represents. public sealed class Expression<tdelegate> : LambdaExpression { // Summary: // Compiles the lambda expression described by the expression tree into executable // code. // // Returns: // A delegate of type TDelegate that represents the lambda expression described // by the System.Linq.Expressions.Expression<tdelegate>. public TDelegate Compile(); } }
复制代码

    表达式树的语法如下:

Expression<Func<type,returnType>> = (param) => lamdaexpresion;

    我们先来看一个简单例子:

Expression<Func<int, int, int>> expr = (x, y) => x+y;

    这就是一个表达式树了。使用Expression Tree Visualizer工具(直接调试模式下看也可以,只不过没这个直观)在调试模式下查看这个表达式树(就是一个对象),如下:

exp_tree

    可以看到表达式树主要由下面四部分组成:

1、Body 主体部分
2、Parameters 参数部分
3、NodeType 节点类型
4、Lambda表达式类型

    对于前面举的例子,主体部分即x+y,参数部分即(x,y)。Lambda表达式类型是Func<Int32, Int32, Int32>。注意主体部分可以是表达式,但是不能包含语句,如下这样:  

Expression<Func<int, int, int>> expr = (x, y) => { return x+y; };

     会报编译错误“Lambada expression with state body cannot be converted to expression tree”:即带有语句的Lambda表达式不能转换成表达式树。

 

    用前面的方法虽然可以创建表达式树,但是不够灵活,如果要灵活构建表达式树,可以像下面这样:

ParameterExpression exp1 = Expression.Parameter(typeof(int), "a"); ParameterExpression exp2 = Expression.Parameter(typeof(int), "b"); BinaryExpression exp = Expression.Multiply(exp1,exp2); var lamExp = Expression.Lambda<Func<int, int, int>>(exp, new ParameterExpression[] { exp1, exp2 });

    exp1、exp2即表达式树的参数,exp是表达式树的主体。如果我利用Reflector反编译Expression<Func<int, int, int>> expr = (x, y) => { return x+y; };得到下面的C#代码:

ParameterExpression CS$0$0000;
ParameterExpression CS$0$0001; Expression<Func<int, int, int>> expr = Expression.Lambda<Func<int, int, int>>(Expression.Multiply(CS$0$0000 = Expression.Parameter(typeof(int), "x"), CS$0$0001 = Expression.Parameter(typeof(int), "y")), new ParameterExpression[] { CS$0$0000, CS$0$0001 });

    可以看到它基本和上面的手动构建代码一致。再来看一个简单的例子:

Expression<Func<Customer, bool>> filter =cust => Equal(Property(cust,"Region"),"North");

    可以用下面的代码手动构建效果等同于上面的表达式树:

复制代码
// declare a parameter of type Customer named cust

ParameterExpression custParam = Expression.Parameter(typeof(Customer), "custParam"); // compare (equality) the Region property of the // parameter against the string constant "North"  BinaryExpression body = Expression.Equal( Expression.Property(custParam, "Region"), Expression.Constant("North", typeof(string))); // formalise this as a lambda  Expression<Func<Customer, bool>> filter = Expression.Lambda<Func<Customer, bool>>(body, cust);
复制代码

    然后我们可以通过表达式树的Compile方法将表达式树编译成Lambda表达式,如下:

Func<Customer, bool> filterFunc = filter.Compile();

    

    但是Compile调用过程涉及动态代码生成,所以出于性能考虑最好只调用一次,然后缓存起来。或者像下面这样在静态构造块中使用(也只会调用一次):

复制代码
public static class Operator<T>
{private static readonly Func<T, T, T> add; public static T Add(T x, T y) { return add(x, y); } static Operator() { var x = Expression.Parameter(typeof(T), "x"); var y = Expression.Parameter(typeof(T), "y"); var body = Expression.Add(x, y); add = Expression.Lambda<Func<T, T, T>>( body, x, y).Compile(); } }
复制代码

    

    Expression类包含下面几类静态方法(.NET 3.5中):

复制代码
Arithmetic: Add, AddChecked, Divide, Modulo, Multiply, MultiplyChecked, Negate, NegateChecked, Power, 
Subtract, SubtractChecked, UnaryPlusCreation: Bind, ElementInit, ListBind, ListInit, MemberBind, MemberInit, New, NewArrayBounds, NewArrayInitBitwise: And, ExclusiveOr, LeftShift (<<), Not, Or, RightShift (>>)Logical: AndAlso (&&), Condition (? :), Equal, GreaterThan, GreaterThanOrEqual, LessThan, 
LessThanOrEqual, NotEqual, OrElse (||), TypeIsMember Access: ArrayIndex, ArrayLength, Call, Field, Property, PropertyOrFieldOther: Convert, ConvertChecked, Coalesce (??), Constant, Invoke, Lambda, Parameter, TypeAs, Quote
复制代码

    下面我们类似前面重载一个浅拷贝的例子(比使用反射开销小):

复制代码
using System;
using System.Linq;
using System.Linq.Expressions; using System.Reflection; namespace ExpressionTreeLab { class Program { static void Main(string[] args) { var p = new Person() { Name = "jxq", Age = 23 }; var shallowCopy = Operator<Person>.ShallowCopy(p); shallowCopy.Name = "feichexia"; Console.WriteLine(shallowCopy.Name); Console.WriteLine(p.Name); Console.ReadKey(); } public class Person { public string Name { get; set; } public int Age { get; set; } } public static class Operator<T> { private static readonly Func<T, T> ShallowClone; public static T ShallowCopy(T sourcObj) { return ShallowClone.Invoke(sourcObj); } static Operator() { var origParam = Expression.Parameter(typeof(T), "orig"); // for each read/write property on T, create a new binding // (for the object initializer) that copies the original's value into the new object var setProps = from prop in typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance) where prop.CanRead && prop.CanWrite select (MemberBinding)Expression.Bind(prop, Expression.Property(origParam, prop)); var body = Expression.MemberInit( // object initializer Expression.New(typeof(T)), // ctor setProps // property assignments  ); ShallowClone = Expression.Lambda<Func<T, T>>(body, origParam).Compile(); } } } }
复制代码

    继续看Expression.AndAlso的使用,它可以用来替代类似下面这种多条件与的情况:

Func<Person, Person, bool> personEqual = (person1, person2) => person1.Name == person2.Name && person1.Age == person2.Age;if(personEqual(p1, p2))
{Console.WriteLine("两个对象所有属性值都相等!"); }

    代码如下:

复制代码
using System;
using System.Linq;
using System.Linq.Expressions; using System.Reflection; namespace ExpressionTreeLab { class Program { static void Main(string[] args) { var p1 = new Person() { Name = "jxq", Age = 23 }; var p2 = new Person() { Name = "jxq", Age = 23 }; if (Operator<Person>.ObjectPropertyEqual(p1, p2)) { Console.WriteLine("两个对象所有属性值都相等!"); } Console.ReadKey(); } public class Person { public string Name { get; set; } public int Age { get; set; } } public static class Operator<T> { private static readonly Func<T, T, bool> PropsEqual; public static bool ObjectPropertyEqual(T obj1, T obj2) { return PropsEqual.Invoke(obj1, obj2); } static Operator() { var x = Expression.Parameter(typeof(T), "x"); var y = Expression.Parameter(typeof(T), "y"); // 获取类型T上的可读Property var readableProps = from prop in typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance) where prop.CanRead select prop; Expression combination = null; foreach (var readableProp in readableProps) { var thisPropEqual = Expression.Equal(Expression.Property(x, readableProp), Expression.Property(y, readableProp)); if(combination == null) { combination = thisPropEqual; } else { combination = Expression.AndAlso(combination, thisPropEqual); } } if(combination == null) // 如果没有需要比较的东西,直接返回false  { PropsEqual = (p1, p2) => false; } else { PropsEqual = Expression.Lambda<Func<T, T, bool>>(combination, x, y).Compile(); } } } } }
复制代码

    在.NET 4.0中扩展了一些Expression的静态方法,使得编写动态代码更容易:

复制代码
Mutation: AddAssign, AddAssignChecked, AndAssign, Assign, DivideAssign, ExclusiveOrAssign, LeftShiftAssign, ModuloAssign, MultiplyAssign, MultiplyAssignChecked, OrAssign, PostDecrementAssign, PostIncrementAssign, PowerAssign, PreDecrementAssign, PreIncrementAssign, RightShiftAssign, SubtractAssign, SubtractAssignCheckedArithmetic: Decrement, Default, Increment, OnesComplementMember Access: ArrayAccess, DynamicLogical: ReferenceEqual, ReferenceNotEqual, TypeEqualFlow: Block, Break, Continue, Empty, Goto, IfThen, IfThenElse, IfFalse, IfTrue, Label, Loop, Return, Switch, SwitchCase, Unbox, VariableExceptions: Catch, Rethrow, ThrowDebug: ClearDebugInfo, DebugInfo
复制代码

    下面是一个利用表达式树编写动态代码的例子(循环打印0到9):

复制代码
using System;
using System.Linq.Expressions;namespace ExpressionTreeLab { class Program { static void Main(string[] args) { var exitFor = Expression.Label("exitFor"); // jump label var x = Expression.Variable(typeof(int), "x"); var body = Expression.Block( new[] { x }, // declare scope variables Expression.Assign(x, Expression.Constant(0, typeof(int))), // init  Expression.Loop( Expression.IfThenElse( Expression.GreaterThanOrEqual( // test for exit  x, Expression.Constant(10, typeof(int)) ), Expression.Break(exitFor), // perform exit Expression.Block( // perform code  Expression.Call( typeof(Console), "WriteLine", null, x), Expression.PostIncrementAssign(x) ) ), exitFor ) // Loop ends  ); var runtimeLoop = Expression.Lambda<Action>(body).Compile(); runtimeLoop(); Console.Read(); } } }
复制代码

    另外WhereIn扩展实现如下,如果前面的例子都熟悉了的话,这个自然也很容易看懂了:

复制代码
    /// <summary>///   使之支持Sql in语法/// </summary> /// <typeparam name = "T"></typeparam> /// <typeparam name = "TValue"></typeparam> /// <param name = "query"></param> /// <param name = "obj"></param> /// <param name = "values"></param> /// <returns></returns> 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); }
复制代码

    调用方式如下:

db.Users.WhereIIn(u => u.Id, new int[] { 1, 2, 3 });

    关于使用表达式树构建LINQ动态查询,请参考Dynamic Linq Queries with Expression Trees

 

参考资料:

http://www.codeproject.com/Tips/438804/Expression-Tree

http://www.infoq.com/articles/expression-compiler

转载于:https://www.cnblogs.com/qq260250932/p/4979325.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/272721.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

程序员的一个好习惯,你有几个?

在很多外人看来,作为我们程序员这个行业只要会敲代码会改bug基本就可以了&#xff0c;但是现在行业人员几近饱和&#xff0c;没有过硬的几把刷子就想着可以甩掉身后的大部队也是不可能的。所以说我们要不断的学习不断的充实自己让企业在裁员的时候压根就考虑不到我们的头上来。…

2017已过半,这半年,你累吗?

大半年下来有人问你挣了多少钱却从来没有人问你最近的日子你累不累近来你受了多少委屈近来有多少心酸近来经历过多少艰难最近付出了多少不被人知的辛苦有时候真的很累、很累但是所有的累只能一笑而过这个累是自己经历的积累这个累是自己身上应该有的责任除了坚强&#xff0c;别…

自学编程的六点技巧

有一天&#xff0c;我的一个在学编程的朋友问我&#xff1a;“我想快速学习编程&#xff0c;你有什么好的推荐吗&#xff1f;我曾在上大学的时候自学过编程&#xff0c;这么多年过去了&#xff0c;我意识到我或许是在用最困难的方式去学习和了解编程。本来我完全可以用更快的速…

信息安全系统设计基础第十周学习总结

第八章 异常控制流 &#xff08;1&#xff09;控制流&#xff1a;控制转移序列。 控制转移&#xff1a;从一条指令到下一条指令。 异常控制流&#xff1a;现代操作系统通过使控制流发生突变来对系统状态做出反应&#xff0c;这些突变称为异常控制流。 &#xff08;2&#xff09…

MVC、 MVP、 MVVM之间的区别

一、 简介三者的目的都是分离关注&#xff0c;使得UI更容易变换&#xff08;从Winform变为Webform&#xff09;&#xff0c;使得UI更容易进行单元测试。二、MVC/MVP1 、MVC1、View接受用户的交互请求2、View将请求转交给Controller3、Controller操作Model进行数据更新4、数据更…

team geek

1. 转载自http://book.douban.com/review/6007037/&#xff0c;版权归丸子(^.^)v所有。 New Google employees (we call “Nooglers”) often ask me what makes me effective at what I do. I tell them only half-jokingly that it’s very simple: I do the Right Thing for…

HDU 3948 不同回文子串个数

集训队论文中有求不同子串个数的做法&#xff0c;就是扫一遍height数组&#xff0c;过程中根据height数组进行去重。对于本题也是雷同的&#xff0c;只是每一次不是根据与排名在上一位的LCP去重&#xff0c;而是与上一次统计对答案有贡献的后缀进行比较去重。 几组数据 abacaba…

oracle 11g重新安装配置,Oracle 11g数据库安装和卸载教程

Oracle11g的安装教程同时解压缩两个zip文件&#xff0c;生成一个database文件夹&#xff0c;进入到database文件夹&#xff0c;点击setup去掉安全更新的选项&#xff0c;直接下一步选择创建和配置数据库&#xff0c;点击下一步选择服务器类&#xff0c;点击下一步选择单例数据库…

程序员职业路线图

今天分享一张程序员职业路线图&#xff1a;欢迎关注我的公众号&#xff08;同步更新文章&#xff09;&#xff1a;DoNet技术分享平台阅读原文

C#编程规范整理

小编整理了一些C#编程的一些规范&#xff0c;希望对大家有点用处&#xff01;1、命名方式Pascal命名法&#xff1a;每个单词首字母均大写。Camel命名法&#xff1a;第一个单词首字母小写&#xff0c;其余单词首字母大写。2、有关类的规范1、使用 Pascal 大小写。2、用名词或名词…

win7 php redis 扩展,Windows中安装Redis及php redis扩展

Windows10环境安装Redis:1、下载Redis安装包&#xff1b;2、解压到自定义目录&#xff1b;3、windows键R&#xff0c;输入cmd 进入命令行&#xff0c;进入Redis的解压目录&#xff1b;4、运行&#xff1a;redis-server.exe redis.windows.conf 显示如下&#xff0c;说明启动成功…

【原创】MVC+ZTree实现权限树的功能

2、html代码权限树&#xff1a;3、JS代码var setting {async: {enable: true,url: /RoleToPerssion/GetPerssionTree,//异步加载时的请求地址autoParam: ["roleid"],//提交参数type: get,dataType: json},check: {enable: true, //true / false 分别表示 显示 / 不…

集合习题之列出有限集合所有子集

1、题目&#xff08;《离散数学及其应用》第6版P75 20 题&#xff09; 给出可以列出有限集合所有子集的步骤。 2、 解题思路 假设有集合A {a1, a2 … an}&#xff0c;列出其所有子集。 先列出含有1个元素的所有子集&#xff1a;{a1},{a2} … {an}然后列出含有2个元素的所有子…

C# partial 关键字的使用

C# 2.0 引入了局部类型的概念。局部类型允许我们将一个类、结构或接口分成几个部分&#xff0c;分别实现在几个不同的.cs文件中。局部类型适用于以下情况&#xff1a;(1) 类型特别大&#xff0c;不宜放在一个文件中实现。(2) 一个类型中的一部分代码为自动化工具生成的代码&…

线段的平移和旋转

//github不会用&#xff0c;试了很久不知道怎么上传代码 #include <iostream> using namespace std;#include <stdlib.h> #include <Eigen/Dense> #include <math.h> using namespace std; using Eigen::MatrixXd; int main() { int option; struct p…

我不问+你不说

阅读原文很多事我不问你不说这就是距离我问了你不说这就是隔阂我问了你说了这就是尊重你想说我想问这就是默契我不问你说了这就是信任很多事情你看到的听到的未必是你想象的那样人生在世多给别人机会解释多些向别人解释的耐心人生会少很多遗憾不问、不说、不解释这不是酷或有个…

怎么安装redhat linux操作系统,红帽RedHat Linux5系统安装指南

介绍如何安装linux操作系统&#xff0c;以目前市场主流的操作系统为例子进行介绍。1、放入安装dvd光盘&#xff0c;然后启动服务器&#xff0c;可得如下画面&#xff1a;2、按enter键&#xff0c;进入如下画面3、选择skip&#xff0c;按enter进入&#xff0c;下面画面&#xff…

研华工控机u盘启动安装linux系统,研华工控机怎么设置u盘启动

本文主要介绍研华IPC如何设置u盘启动研华IPC-610 IPC随XP版一起安装。有时安装控制软件需要在不满意时卸载。卸载未完成&#xff0c;这使得安装无法进行&#xff0c;因此您需要将系统恢复到相对纯粹的时间。通常&#xff0c;USB磁盘启动盘的安装系统首先备份初始纯XP作为备份&a…

UVA - 11732 strcmp() Anyone?左兄弟右儿子trie

input n 2<n<4000 s1 s2 ... sn 1<len(si)<1000 output 输出用strcmp()两两比较si,sj(i!j)要比较的次数&#xff0c;结果在long long范围内&#xff08;相同字符比较两次&#xff0c;不相同字符比较一次&#xff0c;包括\0&#xff09; 做法&#xff1a;由于字符集…

优秀程序员的十个习惯

在这个世界上&#xff0c;有数百万的人热衷于软件开发&#xff0c;他们有很多名字&#xff0c;如&#xff1a;软件工程师&#xff08;Software Engineer&#xff09;&#xff0c;程序员&#xff08;Programmer&#xff09;&#xff0c;编码人&#xff08;Coder&#xff09;&…