您能否列举一些修改表达式树有意义的真实案例?
严格来说,我们从不修改表达式树,因为它们是不可变的(至少从外部看,它不会在内部记忆值或具有可变的私有状态)。正是因为它们是不可变的,因此我们不能仅仅改变一个节点,如果我们想要创建一个基于我们拥有但在某些特定方式上有所不同的新表达式树(最接近我们必须修改不可变对象的事情)。
我们可以在 Linq 本身中找到一些。
在许多方面,最简单的 Linq 提供程序是 linq-to-objects 提供程序,它适用于内存中的可枚举对象。
当它直接接收作为IEnumerable<T>
对象的枚举时,它非常简单,因为大多数程序员可以很快编写大多数方法的未优化版本。例如Where
只是:
foreach (T item in source)
if (pred(item))
yield return item;
等等。但是如何EnumerableQueryable
实现这些IQueryable<T>
版本呢?由于EnumerableQueryable
包装IEnumerable<T>
我们可以对所涉及的一个或多个可枚举对象执行所需的操作,但是我们有一个表达式描述该操作,IQueryable<T>
以及选择器、谓词等的其他表达式,我们需要的是对该操作的描述在IEnumerable<T>
选择器、谓词等方面和委托方面。
System.Linq.EnumerableRewriter
ExpressionVisitor
正是这样一个重写的实现,然后可以简单地编译和执行结果。
在其内部System.Linq.Expressions
,有一些ExpressionVisitor
用于不同目的的实现。一个例子是编译的解释器形式不能直接处理引用表达式中的提升变量,因此它使用访问者将其重写为处理索引到字典中。
除了产生另一个表达式之外,anExpressionVisitor
还可以产生另一个结果。同样System.Linq.Expressions
有内部示例,调试字符串和ToString()
许多表达式类型通过访问有问题的表达式来工作。
这可以(尽管不一定)是数据库查询 linq 提供程序用来将表达式转换为 SQL 查询的方法。
我怎么知道什么时候应该使用它们以及它们应该返回什么?
这些方法的默认实现将:
- 如果表达式不能有子表达式(例如 的结果
Expression.Constant()
),那么它将再次返回节点。
- 否则访问所有子表达式,然后调用
Update
有问题的表达式,将结果传回。Update
反过来,将返回与新子节点相同类型的新节点,或者如果子节点没有更改,则再次返回相同的节点。
因此,如果您不知道出于任何目的需要在节点上显式操作,那么您可能不需要更改它。这也意味着这Update
是获取新版本节点以进行部分更改的便捷方式。但是,“无论你的目的是什么”意味着什么当然取决于用例。最常见的情况可能会走到一个极端或另一个极端,只有一种或两种表达式类型需要覆盖,或者全部或几乎全部都需要它。
(一个警告是,如果您正在检查那些在 a 中具有子节点的节点的子节点,ReadOnlyCollection
例如BlockExpression
其步骤和变量或其TryExpression
捕获块,并且您有时只会更改这些子节点,那么如果您没有更改,那么您就是最好自己检查这是一个缺陷[最近修复,但还没有在任何发布的版本中]意味着如果你将相同的孩子传递Update
到与原始集合不同的集合中,ReadOnlyCollection
那么会不必要地创建一个新的表达式,这会进一步影响树。这通常是无害的,但会浪费时间和内存)。