在这种情况下,我是假人。
我试图在谷歌上读到这些是什么,但我只是不明白。有人可以给我一个简单的解释,说明它们是什么以及为什么它们有用吗?
编辑:我在谈论.Net中的LINQ功能。
答案 0 :(得分:51)
关于我读过的表达树的最佳解释是查理卡尔弗特this article。
总结一下;
表达式树表示您想要执行的 ,而不是 您想要执行的操作。
考虑以下非常简单的lambda表达式:
Func<int, int, int> function = (a, b) => a + b;
本声明由三部分组成:
- 声明:
Func<int, int, int> function
- 等于运算符:
=
- lambda表达式:
(a, b) => a + b;
变量
function
指向原始可执行代码知道如何添加两个数字。
这是委托和表达之间最重要的区别。你打电话给function
(一个Func<int, int, int>
),却不知道它会对你传递的两个整数做什么。这需要两个并返回一个,这是您的代码可以知道的最多。
在上一节中,您了解了如何声明指向原始可执行代码的变量。 表达式树不是可执行代码,它们是数据结构的一种形式。
现在,与代理人不同,您的代码可以知道表达式树的用途。
LINQ提供了一种简单的语法,用于将代码转换为称为表达式树的数据结构。第一步是添加using语句以引入
Linq.Expressions
命名空间:
using System.Linq.Expressions;
现在我们可以创建一个表达式树:
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.
在这里,我们看到我们可以从表达式中获得大量信息。
但为什么我们需要呢?
您已经了解到表达式树是表示可执行代码的数据结构。但到目前为止,我们还没有回答为什么人们想要进行这种转换的核心问题。这是我们在本文开头提出的问题,现在是时候回答了。
在C#程序中不执行LINQ to SQL查询。相反,它被转换为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要容易得多。为了夸大问题的难度,试想将一系列零和一个翻译成SQL!
当需要将查询表达式转换为SQL时,表示查询的表达式树将被拆开并进行分析,就像我们在上一节中拆开了简单的lambda表达式树一样。当然,解析LINQ to SQL表达式树的算法要比我们使用的算法复杂得多,但原理是相同的。一旦它分析了表达式树的各个部分,LINQ就会仔细考虑它们,并决定编写一个返回所请求数据的SQL语句的最佳方法。
创建表达式树是为了将诸如查询表达式之类的代码转换为可以传递给其他进程并在那里执行的字符串。就这么简单。这里没有什么神秘的东西,没有需要挥动的魔杖。只需简单地获取代码,将其转换为数据,然后分析数据以找到将被转换为可传递给另一个进程的字符串的组成部分。
因为查询来自封装在这种抽象数据结构中的编译器,所以编译器可以以任何方式自由地解释它。它不是强制以特定顺序或以特定方式执行查询。相反,它可以分析表达式树,发现你想要做的事情,然后决定如何去做。至少在理论上,它可以自由地考虑任意数量的因素,例如当前的网络流量,数据库上的负载,它可用的当前结果集等。在实践中,LINQ to SQL并未考虑所有这些因素,但理论上它可以自由地做它想要的东西。此外,可以将此表达式树传递给您手动编写的一些自定义代码,这些代码可以对其进行分析并将其转换为与LINQ to SQL生成的内容截然不同的内容。
再次,我们看到表达式树允许我们表示(表达?)我们想要做什么。我们使用的译员决定 我们的表达式如何被使用。
答案 1 :(得分:37)
表达式树是将可执行代码转换为数据的机制。使用表达式树,您可以生成代表您的程序的数据结构。
在C#中,您可以使用Expression<T>
类处理lambda表达式生成的表达式树。
在传统程序中,您可以编写如下代码:
double hypotenuse = Math.Sqrt(a*a + b*b);
此代码使编译器生成一个赋值,就是这样。在大多数情况下,这就是你所关心的。
使用传统代码,您的应用程序无法追溯追溯并查看hypotenuse
以确定它是通过执行Math.Sqrt()
调用生成的;这些信息根本不属于所包含的内容。
现在,考虑一下如下的lambda表达式:
Func<int, int, int> 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)
使用表达式树可以实现更高级的技术和操作。
答案 2 :(得分:14)
表达式树是表达式的内存表示,例如算术或布尔表达式。 例如,考虑算术表达式
a + b*2
由于*的运算符优先级高于+,因此表达式树的构建方式如下:
[+]
/ \
a [*]
/ \
b 2
拥有此树,可以评估a和b的任何值。 此外,您可以将其转换为其他表达式树,例如派生表达式。
实现表达式树时,我建议创建一个基类 表达。由此得出,类 BinaryExpression 将用于所有二进制表达式,例如+和*。然后你可以引入一个 VariableReferenceExpression 来引用变量(比如a和b),然后引入另一个类 ConstantExpression (对于例子中的2)。
表达式树在很多情况下是作为解析输入(直接来自用户或来自文件)的结果而构建的。 为了评估表达式树,我建议使用Visitor pattern。
答案 3 :(得分:13)
简短回答: 很高兴能够编写相同类型的LINQ查询并将其指向任何数据源。没有它,您就无法进行“语言集成”查询。
答案很长: 您可能知道,在编译源代码时,您正在将其从一种语言转换为另一种语言。通常从高级语言(C#)到较低的杠杆(IL)。
基本上有两种方法可以做到这一点:
后者是我们所知道的'编译器'所做的所有程序。
一旦你有一个解析树,你就可以轻松地将它翻译成任何其他语言,这就是表达树允许我们做的事情。由于代码存储为数据,您可以执行任何您想要的操作,但可能您只想将其转换为其他语言。
现在,在LINQ to SQL中,表达式树变为SQL命令,然后通过线路发送到数据库服务器。据我所知,他们在翻译代码时并没有做任何真正想要的事情,但他们可以。例如,查询提供程序可以根据网络条件创建不同的SQL代码。
答案 4 :(得分:5)
IIUC,表达式树类似于抽象语法树,但表达式通常只有一个值,而AST可以表示整个程序(包含类,包,函数,语句等)。
无论如何,对于表达式(2 + 3)* 5,树是:
*
/ \
+ 5
/ \
2 3
递归地评估每个节点(自下而上)以获取根节点处的值,即表达式的值。
当然,你可以使用一元(否定)或三元(if-then-else)运算符,并且如果你的表达式语言允许,你可以使用函数(n-ary,即任意数量的op)。
评估类型和进行类型控制是在类似的树上完成的。
答案 5 :(得分:4)
DLR
表达式树是C#的补充,用于支持动态语言运行时(DLR)。 DLR也负责为我们提供声明变量的“var”方法。 (var objA = new Tree();
)
基本上,Microsoft希望为动态语言打开CLR,例如LISP,SmallTalk,Javascript等。为此,他们需要能够动态地解析和评估表达式。在DLR出现之前,这是不可能的。
回到我的第一句话,表达式树是C#的一个补充,它开启了使用DLR的能力。在此之前,C#是一种更加静态的语言 - 所有变量类型都必须声明为特定类型,并且所有代码都必须在编译时编写。
将其与数据一起使用
表达式树打开动态代码的泛滥门。
例如,假设您正在创建一个房地产网站。在设计阶段,您知道可以应用的所有过滤器。要实现此代码,您有两种选择:您可以编写一个循环,将每个数据点与一系列If-Then检查进行比较;或者您可以尝试使用动态语言(SQL)构建查询,并将其传递给可以执行搜索的程序(数据库)。
使用表达式树,您现在可以在运行中更改程序中的代码并执行搜索。具体来说,您可以通过LINQ执行此操作。
(见更多:MSDN: How to: Use Expression Trees to Build Dynamic Queries)。
超越数据
表达式树的主要用途是用于管理数据。但是,它们也可用于动态生成的代码。所以,如果你想要一个动态定义的函数(ala Javascript),你可以创建一个表达式树,编译它并评估结果。
我会更深入一点,但这个网站做得更好:
Expression Trees as a Compiler
列出的示例包括为变量类型创建泛型运算符,手动滚动lambda表达式,高性能浅层克隆以及将读/写属性从一个对象动态复制到另一个对象。
<强>摘要强>
表达式树是在运行时编译和评估的代码的表示。它们允许动态类型,这对数据操作和动态编程很有用。
答案 6 :(得分:-3)