C# 假人的表达树?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/623413/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-04 10:42:35  来源:igfitidea点击:

Expression trees for dummies?

c#.netlinqexpression-trees

提问by

I am the dummy in this scenario.

在这种情况下,我是假人。

I've tried to read on Google what these are but I just don't get it. Can someone give me a simple explanation of what they are and why they're useful?

我试图在谷歌上阅读这些是什么,但我就是不明白。有人能给我一个关于它们是什么以及它们为什么有用的简单解释吗?

edit: I'm talking about the LINQ feature in .Net.

编辑:我说的是 .Net 中的 LINQ 功能。

回答by Vinay

Is the expression tree that you are refering is Expression Evaluation tree?

您所引用的表达式树是表达式评估树吗?

If yes then it is tree constructed by the parser. Parser used the Lexer/Tokenizer to identify the Tokens from the program. Parser constructs the Binary tree from the tokens.

如果是,那么它是由解析器构造的树。解析器使用 Lexer/Tokenizer 从程序中识别令牌。解析器根据标记构造二叉树。

Hereis the detailed explanation

是详细的解释

回答by Macke

IIUC, an expression tree is similar to an Abstract Syntax Tree, but an expression usually yiels a single value, whereas an AST can represent an entire program (with classes, packages, function, statements, etc.)

IIUC,表达式树类似于抽象语法树,但表达式通常产生单个值,而 AST 可以表示整个程序(包括类、包、函数、语句等)

Anyway, for an the expression (2 + 3) * 5, the tree is:

无论如何,对于表达式 (2 + 3) * 5,树是:

    *
   / \ 
  +   5
 / \
2   3

Evaluate each node recursively (bottom-up) to get the value at the root node, i.e. the value of the expression.

递归地(自下而上)对每个节点求值,得到根节点处的值,即表达式的值。

You can of course have unary (negation) or trinary (if-then-else) operators too, and functions (n-ary, i.e. any number of ops) if your expression language allows it.

如果您的表达式语言允许,您当然也可以使用一元(否定)或三元(if-then-else)运算符和函数(n-ary,即任意数量的操作)。

Evaluating types and doing type-control is done over similar trees.

评估类型和进行类型控制是在相似的树上完成的。

回答by EFrank

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

Since * has a higher operator precedence than +, the expression tree is built like that:

由于 * 的运算符优先级高于 +,因此表达式树的构建方式如下:

    [+]
  /    \
 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.

有了这棵树,就可以对 a 和 b 的任何值进行评估。此外,您可以将其转换为其他表达式树,例如导出表达式。

When you implement an expression tree, I would suggest to create a base class Expression. Derived from that, the class BinaryExpressionwould be used for all binary expressions, such as + and * . Then you could introduce a VariableReferenceExpressionto reference variables (such as a and b), and another class ConstantExpression(for the 2 from the example).

当您实现表达式树时,我建议创建一个基类 Expression。派生自此,类BinaryExpression将用于所有二进制表达式,例如 + 和 * 。然后,您可以引入一个VariableReferenceExpression来引用变量(例如 a 和 b),以及另一个类ConstantExpression(对于示例中的 2)。

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.

在许多情况下,表达式树是作为解析输入(直接来自用户或来自文件)的结果构建的。为了评估表达式树,我建议使用访问者模式

回答by EFrank

An expression tree is a mechanism to translate executable code into data. Using an expression tree, you can produce a data structure that represents your program.

表达式树是一种将可执行​​代码转换为数据的机制。使用表达式树,您可以生成表示程序的数据结构。

In C#, you can work with the expression tree produced by lambda expressions by using the Expression<T>class.

在 C# 中,您可以使用Expression<T>类来处理由 lambda 表达式生成的表达式树。



In a traditional program, you write code like this:

在传统程序中,您编写如下代码:

double hypotenuse = Math.Sqrt(a*a + b*b);

This code causes the compiler to generate an assignment, and that's it. In most cases, that's all you care about.

这段代码使编译器生成一个赋值,就是这样。在大多数情况下,这就是您关心的全部。

With conventional code, your application can't go retroactively back and look at hypotenuseto determine that it was produced by performing a Math.Sqrt()call; this information is simply not part of what is included.

使用传统代码,您的应用程序无法追溯并查看hypotenuse以确定它是通过执行Math.Sqrt()调用生成的;这些信息根本不是所包含内容的一部分。

Now, consider a lambda expression like the following:

现在,考虑如下的 lambda 表达式:

Func<int, int, int> hypotenuse = (a, b) => Math.Sqrt(a*a + b*b);

This is a little different than before. Now hypotenuseis actually a reference to a block of executable code. If you call

这与以前有点不同。现在hypotenuse实际上是对一个可执行代码块的引用。如果你打电话

hypotenuse(3, 4);

you will get the value 5returned.

你会得到5返回的值。

We can use expression treesto explore the block of executable code that was produced. Try this instead:

我们可以使用表达式树来探索生成的可执行代码块。试试这个:

Expression<Func<int, int, int>> addTwoNumbersExpression = (x, y) => x + y;
BinaryExpression body = (BinaryExpression) addTwoNumbersExpression.Body;
Console.WriteLine(body);

This produces:

这产生:

(x + y)

More advanced techniques and manipulations are possible with expression trees.

表达式树可以使用更高级的技术和操作。

回答by Rodrick Chapman

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.

简短回答:能够编写相同类型的 LINQ 查询并将其指向任何数据源真是太好了。没有它,您就无法进行“语言集成”查询。

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).

长答案:您可能知道,当您编译源代码时,您正在将其从一种语言转换为另一种语言。通常从高级语言 (C#) 到低级语言 (IL)。

There are basically two ways you can do this:

基本上有两种方法可以做到这一点:

  1. You can translate the code using find and replace
  2. You parse the code and get a parse tree.
  1. 您可以使用查找和替换来翻译代码
  2. 您解析代码并获得解析树。

The latter is what all the programs we know as 'compilers' do.

后者是我们所知的所有“编译器”程序所做的。

Once you have a parse tree you can easily translate it into any other language and this is what expression trees allow us to do. Since the code is stored as data you can do anything you want to it but probably you'll just want to translate it into some other language.

一旦你有了一个解析树,你就可以轻松地将它翻译成任何其他语言,这就是表达式树允许我们做的事情。由于代码存储为数据,因此您可以对其进行任何操作,但您可能只想将其翻译成其他语言。

Now, in LINQ to SQL the expression trees get turned into a SQL command and then are sent over the wire to the database server. As far as I know they don't do anything really fancy when translating the code but they could. For instance, the query provider could create different SQL code depending on the network conditions.

现在,在 LINQ to SQL 中,表达式树被转换为 SQL 命令,然后通过线路发送到数据库服务器。据我所知,他们在翻译代码时并没有做任何真正花哨的事情,但他们可以。例如,查询提供程序可以根据网络条件创建不同的 SQL 代码。

回答by Richard

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
表达式树是 C# 的补充,用于支持动态语言运行时 (DLR)。DLR 还负责为我们提供声明变量的“var”方法。( var objA = new Tree();)

More on the DLR.

更多关于 DLR 的信息

Essentially, Microsoft wanted to open up the CLR for dynamic languages, such as LISP, SmallTalk, Javascript, etc. To do that, they needed to be able to parse and evaluate expressions on the fly. That was not possible before the DLR came about.

本质上,Microsoft 希望为动态语言(例如 LISP、SmallTalk、Javascript 等)开放 CLR。​​为此,他们需要能够动态解析和评估表达式。在 DLR 出现之前,这是不可能的。

Back to my first sentence, Expression trees are an addition to C# that opens up the ability to use the DLR. Prior to this, C# was a much more static language--all variable types had to be declared as a specific type and all code had to be written at compile time.

回到我的第一句话,表达式树是 C# 的补充,它开启了使用 DLR 的能力。在此之前,C# 是一种更加静态的语言——所有变量类型都必须声明为特定类型,并且所有代码都必须在编译时编写。

Using it with Data
Expression trees opens the flood gates to dynamic code.

将它与数据
表达式树一起使用可以打开通向动态代码的闸门。

Let's say, for example, that you are creating a real-estate site. During the design phase, you know all of the filters that you can apply. To implement this code, you have two choices: you can write a loop that compares each data point to a series of If-Then checks; or you can try to build a query in a dynamic language (SQL) and pass that off to a program that can perform the search for you (the database).

例如,假设您正在创建一个房地产网站。在设计阶段,您知道可以应用的所有过滤器。要实现此代码,您有两种选择:您可以编写一个循环,将每个数据点与一系列 If-Then 检查进行比较;或者您可以尝试使用动态语言 (SQL) 构建查询并将其传递给可以为您执行搜索的程序(数据库)。

With Expression trees, you can now change the code in your program--on the fly--and perform the search. Specifically, you can do this through LINQ.

使用表达式树,您现在可以即时更改程序中的代码并执行搜索。具体来说,您可以通过 LINQ 执行此操作。

(See more: MSDN: How to: Use Expression Trees to Build Dynamic Queries).

(请参阅更多: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.

超越数据
表达式树的主要用途是管理数据。但是,它们也可用于动态生成的代码。所以,如果你想要一个动态定义的函数(ala Javascript),你可以创建一个表达式树,编译它并评估结果。

I would go a bit more in depth, but this site does a much better job:

我会更深入一点,但这个网站做得更好:

Expression Trees as a Compiler

表达式树作为编译器

The examples listed include creating generic operators for variable types, hand-rolling lambda expressions, high performance shallow cloning, and dynamically copying read/write properties from one object to another.

列出的示例包括为变量类型创建泛型运算符、手动滚动 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.

摘要
表达式树是在运行时编译和评估的代码的表示。它们允许动态类型,这对于数据操作和动态编程很有用。

回答by ?afak Gür

The best explanation about expression trees I ever read is this articleby Charlie Calvert.

我读过的关于表达式树的最佳解释是Charlie Calvert 的这篇文章

To sum it up;

把它们加起来;

An expression tree represents whatyou want to do, not howyou want to do it.

表达式树代表什么,你想做的事,而不是如何你想这样做。

Consider the following very simple lambda expression:
Func<int, int, int> function = (a, b) => a + b;

This statement consists of three sections:

  • A declaration: Func<int, int, int> function
  • An equals operator: =
  • A lambda expression: (a, b) => a + b;

The variable functionpoints at raw executable code that knows how to add two numbers.

考虑以下非常简单的 lambda 表达式:
Func<int, int, int> function = (a, b) => a + b;

本声明由三部分组成:

  • 声明: Func<int, int, int> function
  • 等号运算符: =
  • 一个 lambda 表达式: (a, b) => a + b;

变量function指向知道如何将两个数字相加的原始可执行代码。

This is the most important difference between delegates and expressions. You call function(a Func<int, int, int>) without ever knowing what it will do with the two integers you passed. It takes two and returns one, that's the most your code can know.

这是委托和表达式之间最重要的区别。您在调用function(a Func<int, int, int>) 时不知道它会如何处理您传递的两个整数。它需要两个并返回一个,这是您的代码最多可以知道的。

In the previous section, you saw how to declare a variable that points at raw executable code. Expression trees are not executable code, they are a form of data structure.

在上一节中,您看到了如何声明一个指向原始可执行代码的变量。表达式树不是可执行代码,它们是一种数据结构形式。

Now, unlike delegates, your code canknow what an expression tree is meant to do.

现在,与委托不同,您的代码可以知道表达式树的用途。

LINQ provides a simple syntax for translating code into a data structure called an expression tree. The first step is to add a using statement to introduce the Linq.Expressionsnamespace:

using System.Linq.Expressions;

Now we can create an expression tree:
Expression<Func<int, int, int>> expression = (a, b) => a + b;

The identical lambda expression shown in the previous example is converted into an expression tree declared to be of type Expression<T>. The identifier expressionis notexecutable code; it is a data structure called an expression tree.

LINQ 提供了一种简单的语法,用于将代码转换为称为表达式树的数据结构。第一步是添加一个 using 语句来引入Linq.Expressions命名空间:

using System.Linq.Expressions;

现在我们可以创建一个表达式树:
Expression<Func<int, int, int>> expression = (a, b) => a + b;

上一个示例中显示的相同 lambda 表达式被转换为声明为类型的表达式树Expression<T>。标识符expression不是可执行代码;它是一种称为表达式树的数据结构。

That means you can't just invoke an expression tree like you could invoke a delegate, but you can analyze it. So what can your code understand by analyzing the variable 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.

Here we see that there is a great deal of information we can get from an expression.

在这里我们看到,我们可以从表达式中获得大量信息。

But why would we need that?

但我们为什么需要它?

You have learned that an expression tree is a data structure that represents executable code. But so far we have not answered the central question of why one would want to make such a conversion. This is the question we asked at the beginning of this post, and it is now time to answer it.

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

The code found in a query expression has to be translated into a SQL query that can be sent to another process as a string. In this case that process happens to be a SQL server database. It is obviously going to be much easier to translate a data structure such as an expression tree into SQL than it is to translate raw IL or executable code into SQL. To exaggerate the difficulty of the problem somewhat, just imagine trying to translate a series of zeros and ones into SQL!

When it is time to translate your query expression into SQL, the expression tree representing your query is taken apart and analyzed, just as we took apart our simple lambda expression tree in the previous section. Granted, the algorithm for parsing the LINQ to SQL expression tree is much more sophisticated than the one we used, but the principle is the same. Once it has analyzed the parts of the expression tree, then LINQ mulls them over and decides the best way to write a SQL statement that will return the requested data.

Expression trees were created in order to make the task of converting code such as a query expression into a string that can be passed to some other process and executed there. It is that simple. There is no great mystery here, no magic wand that needs to be waved. One simply takes code, converts it into data, and then analyzes the data to find the constituent parts that will be translated into a string that can be passed to another process.

Because the query comes to the compiler encapsulated in such an abstract data structure, the compiler is free to interpret it in almost any way it wants. It is not forced to execute the query in a particular order, or in a particular way. Instead, it can analyze the expression tree, discover what you want done, and then decide how to do it. At least in theory, it has the freedom to consider any number of factors, such as the current network traffic, the load on the database, the current results sets it has available, etc. In practice LINQ to SQL does not consider all these factors, but it is free in theory to do pretty much what it wants. Furthermore, one could pass this expression tree to some custom code you write by hand which could analyze it and translate it into something very different from what is produced by LINQ to SQL.

您已经了解到表达式树是一种表示可执行代码的数据结构。但到目前为止,我们还没有回答为什么要进行这种转换的核心问题。这是我们在本文开头提出的问题,现在是时候回答了。

LINQ to SQL 查询不在 C# 程序中执行。相反,它被翻译成 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 服务器数据库。显然,将表达式树等数据结构转换为 SQL 要比将原始 IL 或可执行代码转换为 SQL 要容易得多。为了稍微夸大问题的难度,想象一下尝试将一系列零和一转换成 SQL!

当需要将您的查询表达式转换为 SQL 时,表示您的查询的表达式树会被拆开并进行分析,就像我们在上一节中拆开我们的简单 lambda 表达式树一样。当然,解析 LINQ to SQL 表达式树的算法比我们使用的算法复杂得多,但原理是相同的。一旦分析了表达式树的各个部分,LINQ 就会仔细考虑它们并决定编写将返回所请求数据的 SQL 语句的最佳方式。

创建表达式树是为了将诸如查询表达式之类的代码转换为可以传递给其他进程并在那里执行的字符串的任务。就是这么简单。这里没有什么大神秘,没有需要挥动的魔杖。只需获取代码,将其转换为数据,然后分析数据以找到将被转换为可以传递给另一个进程的字符串的组成部分。

因为查询来到封装在这样一个抽象数据结构中的编译器,编译器几乎可以以任何它想要的方式自由地解释它。不会强制以特定顺序或特定方式执行查询。相反,它可以分析表达式树,发现你想要做什么,然后决定如何去做。至少在理论上,它可以自由考虑任意数量的因素,例如当前网络流量、数据库负载、当前可用的结果集等。在实践中,LINQ to SQL 并未考虑所有这些因素,但理论上它几乎可以自由地做它想做的事。此外,您可以将此表达式树传递给您手动编写的一些自定义代码,这些代码可以对其进行分析并将其转换为与 LINQ to SQL 生成的内容截然不同的内容。

Once again, we see that the expression trees allow us to represent (express?) whatwe want to do. And we use translators that decide howour expressions are getting used.

再一次,我们看到表达式树允许我们代表(表达什么?)什么,我们想做的事情。我们使用决定译者如何我们的表达式的习惯。