我在这个情境中是个笨蛋。
我尝试在谷歌上了解这些,但我根本不理解。有人能简单地解释一下它们是什么,为什么它们很有用吗?
编辑:我在谈论 .Net 中的 LINQ 功能。
我在这个情境中是个笨蛋。
我尝试在谷歌上了解这些,但我根本不理解。有人能简单地解释一下它们是什么,为什么它们很有用吗?
编辑:我在谈论 .Net 中的 LINQ 功能。
我曾读过的最好的关于表达式树的解释文章是 Charlie Calvert 撰写的这篇文章。
总之;
表达树代表了您想要做什么,而不是您想要如何做。
Consider the following very simple lambda expression:
Func<int, int, int> function = (a, b) => a + b;
这个陈述包括三个部分:
- A declaration:
Func<int, int, int> function
- An equals operator:
=
- A lambda expression:
(a, b) => a + b;
变量
function
指向原始可执行代码,知道如何加两个数字。
这是委托和表达式之间最重要的区别。您不知道调用函数
(一个Func<int,int,int>
)将对您传递的两个整数执行什么操作。它需要两个参数并返回一个结果,这是您的代码所知道的最多的。
在上一节中,您看到了如何声明指向原始可执行代码的变量。表达式树不是可执行代码,它们是一种数据结构。
现在,与代表不同,您的代码可以知道表达式树的目的是什么。
LINQ提供了一种简单的语法,可将代码转换为称为表达式树的数据结构。第一步是添加一个using语句来介绍Linq.Expressions命名空间:
使用 System.Linq.Expressions;
Now we can create an expression tree:
Expression<Func<int, int, int>> expression = (a, b) => a + b;
在前面的示例中显示的相同lambda表达式被转换成一个声明为
Expression<T>
类型的表达式树。标识符expression
不是可执行代码;它是一个称为表达式树的数据结构。
这意味着你不能像调用委托一样调用表达式树,但你可以分析它。因此,通过分析变量expression
,你的代码能理解什么?
// `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.
在这里,我们可以看到从表情中可以获取到大量的信息。
但是我们为什么需要那个?
你已经学到表达式树是代表可执行代码的数据结构。但到目前为止,我们还没有回答为什么想要进行这样的转换的核心问题。这是我们在这篇文章开头所问的问题,现在是回答它的时候了。
A LINQ to SQL query is not executed inside your C# program. Instead, it is translated into SQL, sent across a wire, and executed on a database server. In other words, the following code is never actually executed inside your program:
var query = from c in db.Customers where c.City == "Nantes" select new { c.City, c.CompanyName };
It is first translated into the following SQL statement and then executed on a server:
SELECT [t0].[City], [t0].[CompanyName] FROM [dbo].[Customers] AS [t0] WHERE [t0].[City] = @p0
在查询表达式中发现的代码必须被翻译成一个SQL查询,以字符串的形式发送给另一个进程。在这种情况下,这个进程恰好是一个SQL服务器数据库。将数据结构(例如表达式树)翻译成SQL显然比将原始IL或可执行代码翻译成SQL要容易得多。为了夸大问题的难度,试想一下尝试将一系列的0和1翻译成SQL!
当需要将查询表达式转换为SQL时,查询所代表的表达式树被拆分并分析,就像我们在前一节中拆分简单的lambda表达式树一样。诚然,解析LINQ到SQL表达式树的算法比我们使用的算法要复杂得多,但原理是相同的。一旦分析了表达式树的各个部分,则LINQ会考虑并决定编写一个返回所请求数据的SQL语句的最佳方式。
表达树的创建旨在将代码转换为字符串,以便传递给其他进程并在那里执行,从而简化诸如查询表达式之类的代码转换任务。它就是这么简单。这里没有什么神秘的东西,也不需要使用魔法棒。一个人只需要将代码转换为数据,然后分析数据以找到将被翻译成字符串并传递给另一个进程的组成部分。
因为查询以这种抽象的数据结构封装给编译器,所以编译器可以以几乎任何希望的方式解释它。它不必按一定的顺序或方式执行查询。相反,它可以分析表达式树,发现你想要完成的任务,然后决定如何实现它。至少在理论上,它可以自由考虑任意多个因素,例如当前的网络流量、数据库的负载、它可用的当前结果集等。在实践中,LINQ to SQL并不考虑所有这些因素,但在理论上它可以做几乎任何想做的事情。此外,可以将此表达式树传递给您手动编写的某些自定义代码,该代码可以分析并将其转换为与LINQ to SQL生成的内容非常不同的东西。
再一次,我们看到表达式树允许我们表示(表达)我们想要做什么。我们使用翻译器来决定我们的表达式如何被使用。
表达式树是一种将可执行代码转换为数据的机制。使用表达式树,您可以生成表示您的程序的数据结构。
在C#中,您可以通过使用Expression<T>
类处理由lambda表达式生成的表达式树。
在传统的程序中,您编写的代码可能是这样的:
double hypotenuse = Math.Sqrt(a*a + b*b);
这段代码会让编译器生成一个赋值操作,然后就完成了。在大多数情况下,这就是你所关心的全部内容。
通过常规代码,您的应用无法回溯并查看斜边
以确定它是通过执行Math.Sqrt()
调用产生的;这些信息并不是包含在内的。
现在,请考虑以下lambda表达式:
Func<int, int, double> hypotenuse = (a, b) => Math.Sqrt(a*a + b*b);
这与以前有些不同。现在, hypotenuse
实际上是对可执行代码块的引用。如果您调用
hypotenuse(3, 4);
您将获得返回值5
。
我们可以使用表达式树来探索生成的可执行代码块。试试这个。
Expression<Func<int, int, int>> addTwoNumbersExpression = (x, y) => x + y;
BinaryExpression body = (BinaryExpression) addTwoNumbersExpression.Body;
Console.WriteLine(body);
这产生:
(x + y)
更高级的技术和操作可以通过表达式树实现。
Expression trees are an in-memory representation of an expression, e.g. an arithmetic or boolean expression. For example, consider the arithmetic expression
a + b*2
由于 * 的运算优先级高于 +,所以表达式树是按照这种方式构建的:
[+]
/
a [*]
/
b 2
Having this tree, it can be evaluated for any values of a and b. Additionally, you can transform it into other expression trees, for example to derive the expression.
When you implement an expression tree, I would suggest to create a base class Expression. Derived from that, the class BinaryExpression would be used for all binary expressions, such as + and * . Then you could introduce a VariableReferenceExpression to reference variables (such as a and b), and another class ConstantExpression (for the 2 from the example).
The expression tree is in many cases built as the result of parsing an input (from the user directly, or from a file). For evaluating the expression tree, I would suggest to use the Visitor pattern.
Short answer: It s nice to be able to write the same kind of LINQ query and point it at any data source. You couldn t have a "Language Integrated" query without it.
Long answer: As you probably know, when you compile source code, you re transforming it from one language to another. Usually from a high level language (C#) to a lower lever on (IL).
基本上,您可以用两种方式来做到这一点:
后者是我们所知道的所有编译器程序所做的。
一旦您有语法分析树,您就可以轻松地将其翻译成任何其他语言,这正是表达式树可以让我们做到的。由于代码以数据形式存储,您可以做任何您想做的事情,但可能您只想将其翻译成其他语言。
现在,在LINQ to SQL中,表达式树会被转换成SQL命令,然后发送到数据库服务器。据我所知,在翻译代码时,它们并不真正地做任何花哨的事情,但它们可以。例如,查询提供程序可以根据网络条件创建不同的SQL代码。
IIUC,表达树类似于抽象语法树,但表达通常产生一个单一值,而AST可以表示整个程序(包括类,包,函数,语句等)。
无论如何,表达式(2 + 3)* 5,树形结构如下:
*
/
+ 5
/
2 3
递归地评估每个节点(自下而上)以获取根节点的值,即表达式的值。
当然,如果你的表达式语言允许,你也可以拥有一元(否定)或三元(if-then-else)运算符以及函数(n元,即任意个操作)。
评估类型和进行类型控制是在类似的树上完成的。
The DLR
Expression trees are an addition to C# to support the Dynamic Language Runtime (DLR). The DLR is also what is responsible for giving us the "var" method of declaring variables. (var objA = new Tree();
)
更多关于DLR的内容。
从本质上讲,微软希望为诸如LISP、SmallTalk、Javascript等动态语言开放CLR。为了做到这一点,他们需要能够动态解析和评估表达式。在DLR出现之前,这是不可能的。
回到我第一句话,表达式树是C#的一个补充,它打开了使用DLR的能力。在此之前,C#是一种更加静态的语言 - 所有变量类型都必须声明为特定类型,所有代码都必须在编译时编写。
Using it with Data
Expression trees opens the flood gates to dynamic code.
假设你正在创建一个房地产网站。在设计阶段,你知道所有可以应用的过滤器。要实现这个代码,你有两种选择:你可以编写一个循环来将每个数据点与一系列If-Then检查进行比较;或者你可以尝试在动态语言(SQL)中构建一个查询,并将其传递给可以为你执行搜索的程序(数据库)。
通过表达式树,你现在可以动态地改变程序中的代码并进行搜索。具体来说,你可以通过LINQ实现这一点。
请看更多:MSDN:如何使用表达式树构建动态查询。
Beyond data
The primary uses for Expression Trees are for managing data. However, they can also be used for dynamically generated code. So, if you wanted a function that is defined dynamically (ala Javascript), you can create an Expression Tree, compile it and evaluate the results.
我想解释得更深入一些,但是这个网站做得更好:
将此翻译成中文:表达式树作为编译器
所列举的例子包括为变量类型创建通用运算符,自行创建lambda表达式,高性能的浅层克隆以及动态地从一个对象复制读/写属性到另一个对象。
Summary
Expression Trees are representations of code that is compiled and evaluated at runtime. They allow for dynamic types, which is useful for data manipulation and dynamic programming.