我为什么要使用ExpressionVisitor? [英] Why would I want to use an ExpressionVisitor?

查看:127
本文介绍了我为什么要使用ExpressionVisitor?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我从MSDN的文章中了解到如何:修改表达式树 ExpressionVisitor应该做什么.它应该修改表达式.

I know from the MSDN's article about How to: Modify Expression Trees what an ExpressionVisitor is supposed to do. It should modify expressions.

然而,他们的示例非常不切实际,因此我想知道为什么需要它?您能列举一些现实情况下修改表达式树的意义吗?或者,为什么必须对其进行修改?从什么到什么?

Their example is however pretty unrealistic so I was wondering why would I need it? Could you name some real-world cases where it would make sense to modify an expression tree? Or, why does it have to be modified at all? From what to what?

它也有许多用于访问各种表达式的重载.我怎么知道什么时候应该使用它们以及它们应该返回什么?我看到人们使用VisitParameter并返回base.VisitParameter(node),另一方面返回了Expression.Parameter(..).

It has also many overloads for visiting all kinds of expressions. How do I know when I should use any of them and what should they return? I saw people using VisitParameter and returning base.VisitParameter(node) the other on the other hand were returning Expression.Parameter(..).

推荐答案

您能列举一些在现实中修改表达式树的案例吗?

Could you name some real-world cases where it would make sense to modify an expression tree?

严格来说,我们绝不会修改表达式树,因为它们是不可变的(至少从外部看,没有保证它不会在内部存储值或具有可变的私有状态).正是因为它们是不可变的,因此如果我们要创建一个新的表达式树(基于我们拥有的树,但在某些特定方面有所不同),那么访问者模式就很有意义,因此我们不能仅仅更改结点我们必须修改不可变对象的最接近的东西.

Strictly speaking, we never modify an expression tree, as they are immutable (as seen from the outside, at least, there's no promise that it doesn't internally memoise values or otherwise have mutable private state). It's precisely because they are immutable and hence we can't just change a node that the visitor pattern makes a lot of sense if we want to create a new expression tree that is based on the one we have but different in some particular way (the closest thing we have to modifying an immutable object).

我们可以在Linq本身中找到一些.

We can find a few within Linq itself.

在许多方面,最简单的Linq提供程序是linq-to-objects提供程序,它对内存中的可枚举对象起作用.

In many ways the simplest Linq provider is the linq-to-objects provider that works on enumerable objects in memory.

当它直接接收作为IEnumerable<T>对象的可枚举对象时,它很简单,因为大多数程序员可以很快地编写大多数方法的未优化版本.例如. Where就是:

When it receives enumerables directly as IEnumerable<T> objects it's pretty straight-forward in that most programmers could write an unoptimised version of most of the methods pretty quickly. E.g. Where is just:

foreach (T item in source)
  if (pred(item))
    yield return item;

以此类推.但是,实现IQueryable<T>版本的EnumerableQueryable呢?由于EnumerableQueryable包装了IEnumerable<T>,我们可以对涉及的一个或多个可枚举对象执行所需的操作,但是我们有一个表达式,该表达式根据IQueryable<T>以及选择器,谓词等的其他表达式来描述该操作,其中我们需要的是根据IEnumerable<T>以及选择器,谓词等的委托对该操作进行描述.

And so on. But what about EnumerableQueryable implementing the IQueryable<T> versions? Since the EnumerableQueryable wraps an IEnumerable<T> we could do the desired operation on the one or more enumerable objects involved, but we have an expression describing that operation in terms of IQueryable<T> and other expressions for selectors, predicates, etc, where what we need is a description of that operation in terms of IEnumerable<T> and delegates for selectors, predicates, etc.

ExpressionVisitor的实现,它确实进行了这样的重写,因此可以简单地编译和执行结果.

System.Linq.EnumerableRewriter is an implementation of ExpressionVisitor does exactly such a re-write, and the result can then simply be compiled and executed.

System.Linq.Expressions本身内,有ExpressionVisitor的一些实现用于不同目的.一个例子是,编译器的解释器形式不能直接处理带引号的表达式中的提升变量,因此它使用访问者将其重写以处理字典中的索引.

Within System.Linq.Expressions itself there are a few implementations of ExpressionVisitor for different purposes. One example is that the interpreter form of compilation can't handle hoisted variables in quoted expressions directly, so it uses a visitor to rewrite it into working on indices into a a dictionary.

除了产生另一个表达式外,ExpressionVisitor也可以产生另一个结果.再次,System.Linq.Expressions本身具有内部示例,其中包含调试字符串,并且ToString()可以通过访问有问题的表达式来处理许多表达式类型.

As well as producing another expression, an ExpressionVisitor can produce another result. Again System.Linq.Expressions has internal examples itself, with debug strings and ToString() for many expression types working by visiting the expression in question.

这可以(尽管不一定)是数据库查询linq提供程序用来将表达式转换为SQL查询的方法.

This can (though it doesn't have to be) be the approach used by a database-querying linq provider to turn an expression into a SQL query.

我怎么知道什么时候应该使用它们,它们又应该返回什么?

How do I know when I should use any of them and what should they return?

这些方法的默认实现将是:

The default implementation of these methods will:

  1. 如果表达式不能有子表达式(例如Expression.Constant()的结果),则它将再次返回该节点.
  2. 否则,请访问所有子表达式,然后在有问题的表达式上调用Update,将结果传回. Update反过来将返回与新子节点具有相同类型的新节点,或者如果未更改子节点,则再次返回同一节点.
  1. If the expression can have no child expressions (e.g. the result of Expression.Constant()) then it will return the node back again.
  2. Otherwise visit all the child expressions, and then call Update on the expression in question, passing the results back. Update in turn will either return a new node of the same type with the new children, or return the same node back again if the children weren't changed.

这样,如果您不知道出于任何目的需要在节点上进行显式操作,则可能无需更改它.这也意味着Update是获取节点的新版本以进行部分更改的便捷方法.但是,无论您打算做什么"的含义当然取决于用例.最常见的情况可能是一种极端,另一种极端,要么仅一种或两种表达式类型需要覆盖,要么全部或几乎全部都需要覆盖.

As such, if you don't know you need to explicitly operate on a node for whatever your purposes are, then you probably don't need to change it. It also means that Update is a convenient way to get a new version of a node for a partial change. But just what "whatever your purposes are" means of course depends on the use case. The most common cases are probably go to one extreme or the other, with either just one or two expression types needing an override, or all or nearly all needing it.

(一个警告是,如果您正在检查在ReadOnlyCollection中具有子级的那些节点的子级,例如BlockExpression的步和变量,或者TryExpression的catch块,那么您有时只会更改那些子项,然后如果您没有更改,则最好自己检查一下是否存在缺陷(最近已修复,但尚未发布),这意味着如果您将相同的子项传递给不同集合中的Update,原始的ReadOnlyCollection会不必要地创建一个新的表达式,该表达式对树的影响更大.这通常是无害的,但会浪费时间和内存.

(One caveat is if you are examining the children of those nodes that have children in a ReadOnlyCollection such as BlockExpression for both its steps and variables or TryExpression for its catch-blocks, and you will only sometimes change those children then if you haven't changed you are best to check for this yourself as a flaw [recently fixed, but not in any released version yet] means that if you pass the same children to Update in a different collection to the original ReadOnlyCollection then a new expression is created needlessly which has effects further up the tree. This is normally harmless, but it wastes time and memory).

这篇关于我为什么要使用ExpressionVisitor?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆