我是这个场景中的傀儡。
我试着在谷歌上读到这些是什么,但我就是不明白。谁能给我一个简单的解释,说明它们是什么以及它们为什么有用?
编辑:我说的是.Net中的LINQ特性。
发布于 2013-12-25 04:00:11
我读过的关于表达式树的最好的解释是Charlie Calvert的this article。
总结一下;
表达式树表示你想做什么,而不是你想做什么,你想做什么。
考虑以下非常简单的lambda表达式:
Func<int, int, int> function = (a, b) => a + b;
该语句由三个部分组成:
表达式声明:Func<int, int, int> function
=
(a, b) => a + b;变量function指向知道如何将两个数字相加的原始可执行代码。
这是委托和表达式之间最重要的区别。您调用function (一个Func<int, int, int>),而不知道它将如何处理您传递的两个整数。它接受2并返回1,这就是你的代码所能知道的最多信息。
在上一节中,您看到了如何声明指向原始可执行代码的变量。表达式树不是可执行代码,它们是数据结构的一种形式。
现在,与委托不同的是,您的代码可以知道表达式树的用途。
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容易得多。为了在某种程度上夸大这个问题的难度,想象一下试图将一系列0和1转换成SQL!
当需要将查询表达式转换为SQL时,表示查询的表达式树将被拆分并进行分析,就像我们在上一节中拆解简单的lambda表达式树一样。诚然,解析LINQ to SQL表达式树的算法比我们使用的算法复杂得多,但原理是相同的。一旦它分析了表达式树的各个部分,LINQ就会仔细考虑它们,并决定编写返回所请求数据的SQL语句的最佳方式。
创建表达式树的目的是完成将查询表达式等代码转换为可传递给其他进程并在其中执行的字符串的任务。就是这么简单。这里没有什么神秘的东西,也没有需要挥舞的魔棒。只需获取代码,将其转换为数据,然后分析数据以找到将被转换为可传递给另一个进程的字符串的组成部分。
因为查询被封装在这样一个抽象数据结构中的编译器,所以编译器可以自由地以几乎任何它想要的方式解释它。不会强制它以特定的顺序或特定的方式执行查询。相反,它可以分析表达式树,发现您想要做什么,然后决定如何做。至少在理论上,它可以自由地考虑任何数量的因素,例如当前的网络流量、数据库上的负载、当前可用的结果集等。在实践中,LINQ to SQL不会考虑所有这些因素,但在理论上它可以自由地做它想做的事情。此外,您可以将此表达式树传递给您手动编写的一些自定义代码,这些代码可以对其进行分析,并将其转换为与LINQ to SQL生成的内容非常不同的内容。
再一次,我们看到表达式树允许我们表示(express?) 我们想要做的。我们使用翻译器来决定如何使用我们的表达式()。
发布于 2009-03-08 11:26:04
表达式树是一种将可执行代码转换为数据的机制。使用表达式树,您可以生成表示程序的数据结构。
在C#中,您可以通过使用Expression<T>类来处理由lambda表达式生成的表达式树。
在传统程序中,您可以像这样编写代码:
double hypotenuse = Math.Sqrt(a*a + b*b);这段代码会导致编译器生成一个赋值,仅此而已。在大多数情况下,这就是你所关心的。
对于传统的代码,您的应用程序不能回溯并查看hypotenuse来确定它是通过执行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)使用表达式树可以实现更高级的技术和操作。
发布于 2009-03-08 11:25:40
表达式树是表达式的内存表示,例如算术或布尔表达式。例如,考虑算术表达式
a + b*2由于*具有比+更高的运算符优先级,因此表达式树是这样构建的:
[+]
/ \
a [*]
/ \
b 2有了这个树,就可以对a和b的任何值进行求值。此外,您可以将其转换为其他表达式树,例如派生表达式。
在实现表达式树时,我建议创建一个基类表达式。由此派生出的类BinaryExpression将用于所有的二进制表达式,如+和*。然后,您可以引入一个引用变量(例如a和b)的VariableReferenceExpression,以及另一个类ConstantExpression (对于示例中的2)。
在许多情况下,表达式树是作为解析输入(直接来自用户或来自文件)的结果而构建的。为了计算表达式树,我建议使用Visitor pattern。
https://stackoverflow.com/questions/623413
复制相似问题