表达式类的目的是什么?

76

我想知道在使用Expression<>和不使用之间的区别是什么?

我经常看到在LinQ中大量使用Expression<Foo>,但到目前为止,我还没有找到任何解释这种方式与只使用委托之间差异的文章。

例如:

Func<int, bool> Is42 = (value) => value == 42;

对比。

Expression<Func<int, bool>> Is42 = (value) => value == 42;
6个回答

82

tl;dr, 持有一个表达式就像持有一个应用程序的源代码,而委托则是运行该应用程序的可执行文件。表达式就像是将要运行的代码的“源”(即语法树)。委托是您将要运行的特定编译版本并完成操作的内容。


通过将lambda表达式存储为委托,您正在存储一个执行某个操作的特定委托实例。它无法修改,只能调用它。一旦获得委托,检查其所做的事情和其他信息的选项有限。

通过将lambda表达式存储为表达式,您正在存储代表委托的表达式树。可以对其进行操作以执行其他操作,例如更改其参数,更改主体并使其执行完全不同的操作。甚至可以将其重新编译为委托,因此如果需要,可以调用它。您可以轻松地检查表达式以查看其参数,它所做的事情以及它如何执行该操作。这是查询提供者可用于理解和将表达式转换为另一种语言(例如编写相应表达式树的SQL查询)的功能。

使用表达式动态创建委托比发出代码要容易得多。您可以将代码视为类似于编译器查看代码的表达式,而不是将代码视为低级别的IL指令。

因此,通过表达式,您能够做比简单的匿名委托更多的事情。虽然这并非免费,但是如果与常规方法或匿名委托相比运行已编译的表达式,性能会受到影响。但是,如果使用表达式的其他优点对您很重要,那么可能不会有问题。


你能给出一个具体的例子,说明使用Expression<>有明显的优势吗?我在谷歌上找到的所有示例都很简单,我没有看到任何优势。 - David Thielen
通用表达式是一种指示非常特定类型的表达式的方式,它是一种可以编译为委托的类型化 lambda 表达式。请注意,通用表达式派生自“Expression”,因此它仍然是一个表达式,并非每个“Expression”都可以是通用的。与“IEnumerable”和“IEnumerable<T>”类似,没有可用的类型与“Expression”相关联。[示例](https://i.stack.imgur.com/RCplD.png)当您想通过构建这些表达式来在运行时创建函数而不使用 CodeDom 或其他类似方法时,这非常有用。 - Jeff Mercado
这也是在linq中使用IQueryable<T>的主要构建块。由于查询是有类型的,因此需要该类型上的表达式。因此,为表达式提供类型允许更容易地实现查询提供程序,在其中可以在编译时强制执行类型。 - Jeff Mercado
好的,我显然没有理解某些基本的东西。为什么我不直接编写一个委托而是需要编译一个表达式来获取一个委托呢?在我看到的所有示例中,表达式似乎只是增加了一步。 - David Thielen
这可能更适合作为一个单独的问题。这可能已经被问过并且有了一个很好的答案。除了我在这里提到的动态代码生成之外,还有许多很棒的用例。 - Jeff Mercado

17

Func<>只是一个委托类型。Expression是操作的完整树的运行时表示形式,可以在运行时编译为委托(可选)。正是这个树被表达式解析器(例如Linq-to-SQL)解析,以生成SQL语句或执行其他聪明的操作。当您将lambda分配给Expression类型时,编译器会生成此表达式树以及通常的IL代码。关于表达式树的更多信息


14
为了说明其他答案,如果编译这两个表达式并查看编译器生成的代码,你会看到以下内容: Func<int, bool> Is42 = (value) => value == 42;
Func<int, bool> Is42 = new Func<int, bool>((@value) => value == 42);


Expression<Func<int, bool>> Is42 = (value) => value == 42;


Expression<Func<int, bool>> Is42 = (value) => value == 42;

ParameterExpression[] parameterExpressionArray;
ParameterExpression parameterExpression = Expression.Parameter(typeof(int), "value");
Expression<Func<int, bool>> Is42 = Expression.Lambda<Func<int, bool>>(Expression.Equal(parameterExpression, Expression.Constant(42, typeof(int))), new ParameterExpression[] { parameterExpression });

6
他在询问有什么不同之处,而生成的代码已经很清楚地展示了这些不同。 - Ucodia

5

表达式树允许您在代码中检查表达式内部的代码。

例如,如果您传递了这个表达式:o => o.Name,您的代码可以发现表达式内部正在访问Name属性。


4
提供基类,用于派生表示表达式树节点的类。
System.Linq.Expressions.BinaryExpression
System.Linq.Expressions.BlockExpression
System.Linq.Expressions.ConditionalExpression
System.Linq.Expressions.ConstantExpression
System.Linq.Expressions.DebugInfoExpression
System.Linq.Expressions.DefaultExpression
System.Linq.Expressions.DynamicExpression
System.Linq.Expressions.GotoExpression
System.Linq.Expressions.IndexExpression
System.Linq.Expressions.InvocationExpression
System.Linq.Expressions.LabelExpression
System.Linq.Expressions.LambdaExpression
System.Linq.Expressions.ListInitExpression
System.Linq.Expressions.LoopExpression
System.Linq.Expressions.MemberExpression
System.Linq.Expressions.MemberInitExpression
System.Linq.Expressions.MethodCallExpression
System.Linq.Expressions.NewArrayExpression
System.Linq.Expressions.NewExpression
System.Linq.Expressions.ParameterExpression
System.Linq.Expressions.RuntimeVariablesExpression
System.Linq.Expressions.SwitchExpression
System.Linq.Expressions.TryExpression
System.Linq.Expressions.TypeBinaryExpression
System.Linq.Expressions.UnaryExpression

表达式树表示可以分析并且例如转换成SQL查询的Linq表达式。您可以参考此链接了解更多信息:http://msdn.microsoft.com/en-us/library/system.linq.expressions.expression.aspx

1

除了其他人写的(完全正确)之外,我要补充一点,通过Expression类,您可以在运行时创建新方法。有一些限制。并不是所有您可以在C#中做的事情都可以在Expression树中完成(至少在.NET 3.5中。随着.NET 4.0的推出,他们添加了大量可能的Expression“类型”)。使用它的好处可能是(例如)创建动态查询并将其传递给LINQ-to-SQL或根据用户输入进行一些过滤...(如果您只想要与LINQ-to-SQL不兼容的动态方法,则始终可以使用CodeDom来实现此目的,但直接发出IL代码相当困难 :-))


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接