用于假人的expression树?
我是这种情况下的假人。
我试图在谷歌上看到这些是什么,但我只是不明白。 有人能给我一个简单的解释,说明他们是什么,为什么他们有用吗?
编辑:我正在谈论.NET中的LINQfunction。
expression式树是将可执行代码转换为数据的机制。 使用expression式树,您可以生成一个表示程序的数据结构。
在C#中,可以使用Expression<T>
类使用由lambdaexpression式生成的expression式树。
在传统的程序中,你可以这样写代码:
double hypotenuse = Math.Sqrt(a*a + b*b);
此代码会导致编译器生成一个分配,就是这样。 在大多数情况下,这就是你所关心的。
使用传统的代码,你的应用程序不能追溯回去,看hypotenuse
以确定它是通过执行Math.Sqrt()
调用产生的; 这些信息根本不包括在内。
现在,考虑如下的lambdaexpression式:
Func<int, int, int> hypotenuse = (a, b) => Math.Sqrt(a*a + b*b);
这与以前有些不同。 现在hypotenuse
实际上是对可执行代码块的引用。 如果你打电话
hypotenuse(3, 4);
你会得到返回的值5
。
我们可以使用expression式树来探索生成的可执行代码块。 试试这个:
Expression<Func<int, int, int>> addTwoNumbersExpression = (x, y) => x + y; BinaryExpression body = (BinaryExpression) addTwoNumbersExpression.Body; Console.WriteLine(body);
这产生:
(x + y)
expression式树可以使用更高级的技术和操作。
查理·卡尔弗特(Charlie Calvert)的这篇文章对我所阅读过的expression树做了最好的解释。
把它们加起来;
expression式树表示你想要做什么,而不是你想做什么。
考虑以下非常简单的lambdaexpression式:
Func<int, int, int> function = (a, b) => a + b;
本声明由三部分组成:
- 声明:
Func<int, int, int> function
- 等于运算符:
=
- 拉姆达expression式:
(a, b) => a + b;
可变
function
指向原始可执行代码, 知道如何添加两个数字 。
这是代表和expression之间最重要的区别。 你调用function
而不知道你将通过的两个整数将做什么。 它需要两个并返回一个,这是你的代码可以知道的最多。
在前一节中,您看到了如何声明一个指向原始可执行代码的variables。 expression式树不是可执行代码 ,它们是数据结构的一种forms。
现在,与代表不同,代码可以知道expression式树的意图。
LINQ提供了一个简单的语法来将代码翻译成称为expression式树的数据结构。 第一步是添加一个using语句来介绍
Linq.Expressions
命名空间:
using System.Linq.Expressions;
现在我们可以创build一个expression式树:
Expression<Func<int, int, int>> expression = (a, b) => a + b;
前面例子中所示的相同的lambdaexpression式被转换成一个expression式树,被声明为
Expression<T>
types。 标识符expression
不是可执行代码; 它是一个称为expression式树的数据结构。
这意味着您不能像调用委托一样调用expression式树,但可以分析它。 那么通过分析variablesexpression
你的代码能理解什么?
// `expression.NodeType` returns NodeType.Lambda. // `expression.Type` returns Func<int, int, int>. // `expression.ReturnType` returns Int32. var body = expression.Body; // `body.NodeType` returns ExpressionType.Add. // `body.Type` returns System.Int32. var parameters = expression.Parameters; // `parameters.Count` returns 2. var firstParam = parameters[0]; // `firstParam.Name` returns "a". // `firstParam.Type` returns System.Int32. var secondParam = parameters[1]. // `secondParam.Name` returns "b". // `secondParam.Type` returns System.Int32.
在这里我们看到,我们可以从一个expression中获得大量的信息。
但为什么我们需要这个?
您已经了解到expression式树是表示可执行代码的数据结构。 但是到目前为止,我们还没有回答为什么要进行这种转换的核心问题。 这是我们在这篇文章开头提出的问题,现在是时候回答了。
LINQ to SQL查询不会在C#程序中执行。 相反,它被翻译成SQL,通过networking发送,并在数据库服务器上执行。 换句话说,下面的代码永远不会在你的程序中被执行:
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
在查询expression式中find的代码必须被转换成一个SQL查询,可以作为一个string发送到另一个进程。 在这种情况下,该进程碰巧是一个SQL服务器数据库。 将数据结构(如expression式树)转换为SQL比将原始IL或可执行代码转换为SQL显然要容易得多。 为了夸大这个问题的难度,想象一下,把一系列的零和一些转换成SQL!
当将查询expression式转换为SQL时,代表您的查询的expression式树被拆开并分析,正如我们在前一节中拆分了简单的lambdaexpression式树。 当然,parsingLINQ to SQLexpression式树的algorithm比我们使用的algorithm复杂得多,但是原理是一样的。 一旦它分析了expression式树的部分,然后LINQ将它们放在一边,决定编写一个将返回请求的数据的SQL语句的最佳方式。
创buildexpression式树是为了将代码(如查询expression式)转换为可传递给其他进程并在其中执行的string。 这很简单。 这里没有什么神秘的东西,没有魔杖需要挥手。 一个简单的代码,把它转换成数据,然后分析数据,find组成部分,将被转换成可以传递到另一个进程的string。
由于查询是以封装在这种抽象数据结构中的编译器的forms出现的,因此编译器可以自由地以任何想要的方式解释它。 并不是强制以特定的顺序或以特定的方式执行查询。 相反,它可以分析expression树,发现你想要做什么,然后决定如何去做。 至less在理论上,它可以自由地考虑许多因素,比如当前的networkingstream量,数据库上的负载,当前可用的结果集等等。实际上,LINQ to SQL并不考虑所有这些因素,但从理论上来讲,它可以自由地做所需要的事情。 此外,可以将此expression式树传递给您手动编写的一些自定义代码,以便将其分析并将其转换为与LINQ to SQL生成的代码非常不同的代码。
我们再次看到expression式树使我们能够expression(expression)我们想要做的事情。 我们使用翻译来决定我们的expression如何被执行。
expression式树是expression式的内存中表示,例如算术或布尔expression式。 例如,考虑算术expression式
a + b*2
由于*具有比+更高的运算符优先级,因此expression式树就像这样构build:
[+] / \ a [*] / \ b 2
拥有这棵树,可以评估a和b的任何值。 此外,您可以将其转换为其他expression式树,例如派生expression式。
当你实现一个expression式树时,我会build议创build一个基类Expression 。 派生自此类的BinaryExpression类将用于所有二进制expression式,如+和*。 然后你可以引入一个VariableReferenceExpression来引用variables(比如a和b),另一个类ConstantExpression (例子中的2)。
expression式树在许多情况下是作为parsinginput(从用户直接或从文件中)的结果而构build的。 为了评估expression式树,我build议使用Visitor模式 。
简短的回答:很高兴能够编写相同types的LINQ查询并将其指向任何数据源。 没有它,你不能有一个“语言集成”的查询。
长的回答:正如你可能知道的那样,当你编译源代码时,你正在把它从一种语言转换到另一种语言。 通常从高级语言(C#)到低级(IL)。
基本上有两种方法可以做到这一点:
- 您可以使用查找和replace来翻译代码
- 你parsing代码并得到一个分析树。
后者就是我们所知道的“编译器”所做的所有程序。
一旦你有一个parsing树,你可以很容易地翻译成任何其他语言,这是expression式树让我们做的。 由于代码是作为数据存储的,你可以做任何你想做的事情,但是可能你只是想把它翻译成其他语言。
现在,在LINQ to SQL中,expression式树变成了SQL命令,然后通过线路发送到数据库服务器。 据我所知,在翻译代码时他们没有做任何特别的事情,但他们可以 。 例如,查询提供者可以根据networking条件创build不同的SQL代码。
IIUC,expression式树与抽象语法树类似,但expression式通常只有一个值,而AST可以表示整个程序(包括类,包,函数,语句等)
无论如何,对于expression式(2 + 3)* 5,树是:
* / \ + 5 / \ 2 3
recursion地评估每个节点(自底向上)以获取根节点的值,即expression式的值。
你当然可以有一元(否定)或三元(if-then-else)操作符,并且如果你的expression式语言允许的话,它可以运行(n元,即任意数量的操作)。
评估types和做types控制是在类似的树上完成的。
DLR
expression式树是C#的补充,支持dynamic语言运行时(DLR)。 DLR也是负责给我们提供variables声明的“var”方法。 ( var objA = new Tree();
)
更多关于DLR 。
本质上,微软希望为dynamic语言(如LISP,SmallTalk,Javascript等)打开CLR。为此,他们需要能够dynamicparsing和评估expression式。 在DLR出现之前,这是不可能的。
回到我的第一句话,Expression树是C#的一个补充,它开放了使用DLR的能力。 在此之前,C#是一个更静态的语言 – 所有variablestypes必须被声明为特定的types,所有的代码必须在编译时写入。
在数据中使用它
expression式树将dynamic代码打开。
比方说,你正在创build一个房地产网站。 在devise阶段,您知道所有可以应用的filter。 要实现这个代码,你有两个select:你可以编写一个循环,将每个数据点与一系列If-Then检查进行比较; 或者您可以尝试使用dynamic语言(SQL)构build查询,然后将其传递给可以执行search的程序(数据库)。
使用expression式树,您现在可以更改程序中的代码 – 即时执行 – 并执行search。 具体来说,你可以通过LINQ来做到这一点。
(查看更多: MSDN:如何使用expression式树来构builddynamic查询 )
超越数据
expression式树的主要用途是pipe理数据。 但是,它们也可以用于dynamic生成的代码。 所以,如果你想要一个dynamic定义的函数(ala Javascript),你可以创build一个expression式树,编译它并评估结果。
我会深入一点,但这个网站做得更好:
expression式树作为编译器
列出的示例包括为variablestypes创buildgenerics运算符,手动滚动lambdaexpression式,高性能浅层克隆以及将读/写属性从一个对象dynamic复制到另一个对象。
概要
expression式树是在运行时编译和评估的代码的表示forms。 它们允许dynamictypes,这对数据操作和dynamic编程非常有用。
您正在引用的expression式树是expression式评估树吗?
如果是,那么它是parsing器构造的树。 parsing器使用Lexer / Tokenizer从程序中识别令牌。 parsing器从令牌构造二叉树。
这里是详细的解释