在这种情况下我是假的.
我试图在谷歌上读到这些是什么,但我只是不明白.有人可以给我一个简单的解释,说明它们是什么以及为什么它们有用?
编辑:我在谈论.Net中的LINQ功能.
关于我读过的表达树的最佳解释是查理卡尔弗特的这篇文章.
把它们加起来;
表达式树代表什么,你想做的事,而不是如何你想这样做.
考虑以下非常简单的lambda表达式:
Func
function = (a, b) => a + b; 本声明由三部分组成:
声明:
Func
function 一个等于运算符:
=
lambda表达式:
(a, b) => a + b;
变量
function
指向原始可执行代码,知道如何添加两个数字.
这是委托和表达之间最重要的区别.你打电话给function
(a Func
)而不知道它会对你传递的两个整数做什么.它需要两个并返回一个,这是您的代码可以知道的最多.
在上一节中,您了解了如何声明指向原始可执行代码的变量.表达式树不是可执行代码,它们是数据结构的一种形式.
现在,与代理不同,您的代码可以知道表达式树的用途.
LINQ提供了一种简单的语法,用于将代码转换为称为表达式树的数据结构.第一步是添加using语句来引入
Linq.Expressions
命名空间:
using System.Linq.Expressions;
现在我们可以创建一个表达式树:
Expression
> expression = (a, b) => a + b; 前一个示例中显示的相同lambda表达式将转换为声明为type的表达式树
Expression
.标识符expression
不是可执行代码; 它是一种称为表达式树的数据结构.
这意味着您不能像调用委托一样调用表达式树,但您可以对其进行分析.那么你的代码可以通过分析变量来理解什么expression
?
// `expression.NodeType` returns NodeType.Lambda. // `expression.Type` returns Func. // `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生成的内容截然不同的代码.
再一次,我们看到表达式树允许我们代表(表达什么?)什么,我们想做的事情.我们使用翻译来决定如何使用表达式.
表达式树是将可执行代码转换为数据的机制.使用表达式树,您可以生成表示程序的数据结构.
在C#中,您可以使用Expression
类来处理由lambda表达式生成的表达式树.
在传统程序中,您编写如下代码:
double hypotenuse = Math.Sqrt(a*a + b*b);
这段代码使编译器生成一个赋值,就是这样.在大多数情况下,这就是你所关心的.
使用传统代码,您的应用程序无法追溯追溯并查看hypotenuse
确定它是通过执行Math.Sqrt()
调用生成的; 这些信息根本不属于所包含的内容.
现在,考虑一个lambda表达式,如下所示:
Funchypotenuse = (a, b) => Math.Sqrt(a*a + b*b);
这与以前略有不同.现在hypotenuse
实际上是对可执行代码块的引用.如果你打电话
hypotenuse(3, 4);
你会得到5
返回的值.
我们可以使用表达式树来探索生成的可执行代码块.试试这个:
Expression> addTwoNumbersExpression = (x, y) => x + y; BinaryExpression body = (BinaryExpression) addTwoNumbersExpression.Body; Console.WriteLine(body);
这会产生:
(x + y)
使用表达式树可以实现更高级的技术和操作.
表达式树是表达式的内存表示,例如算术或布尔表达式.例如,考虑算术表达式
a + b*2
由于*具有比+更高的运算符优先级,因此表达式树的构建方式如下:
[+] / \ a [*] / \ b 2
拥有此树,可以评估a和b的任何值.此外,您可以将其转换为其他表达式树,例如派生表达式.
当您实现表达式树时,我建议创建一个基类 Expression.派生自此类,BinaryExpression类将用于所有二进制表达式,例如+和*.然后,您可以引入VariableReferenceExpression来引用变量(例如a和b),以及另一个类ConstantExpression(来自示例中的2).
在许多情况下,表达式树是作为解析输入(直接来自用户或来自文件)的结果而构建的.为了评估表达式树,我建议使用访问者模式.
简短回答:很高兴能够编写相同类型的LINQ查询并将其指向任何数据源.没有它,您就无法进行"语言集成"查询.
答案很清楚:正如您可能知道的,当您编译源代码时,您正在将其从一种语言转换为另一种语言.通常从高级语言(C#)到较低的杠杆(IL).
基本上有两种方法可以做到这一点:
您可以使用find和replace来翻译代码
您解析代码并获得解析树.
后者是我们所知道的"编译器"所做的所有程序.
一旦你有一个解析树,你就可以轻松地将它翻译成任何其他语言,这就是表达树允许我们做的事情.由于代码存储为数据,您可以执行任何您想要的操作,但可能您只想将其转换为其他语言.
现在,在LINQ to SQL中,表达式树变为SQL命令,然后通过线路发送到数据库服务器.据我所知,他们在翻译代码时并没有做任何真正的事情,但他们可以.例如,查询提供程序可以根据网络条件创建不同的SQL代码.
IIUC,表达式树类似于抽象语法树,但表达式通常只有一个值,而AST可以表示整个程序(包含类,包,函数,语句等)
无论如何,对于表达式(2 + 3)*5,树是:
* / \ + 5 / \ 2 3
递归地评估每个节点(自下而上)以获取根节点处的值,即表达式的值.
你当然可以有一元(否定)或三元(if-then-else)运算符,并且如果表达式语言允许,它们还可以运行(n-ary,即任意数量的运算).
评估类型和进行类型控制是在类似的树上完成的.