用于假人的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)。

基本上有两种方法可以做到这一点:

  1. 您可以使用查找和replace来翻译代码
  2. 你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器从令牌构造二叉树。

这里是详细的解释