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).
We can find a few within Linq itself.
In many ways the simplest Linq provider is the linq-to-objects provider that works on enumerable objects in memory.
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;
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.
System.Linq.EnumerableRewriter
is an implementation of ExpressionVisitor
does exactly such a re-write, and the result can then simply be compiled and executed.
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.
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.
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:
- If the expression can have no child expressions (e.g. the result of
Expression.Constant()
) then it will return the node back again.
- 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.
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.
(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
from MS but their own implementation, who knows what they were thinking ;-] – Weiderinternal
back in the day, I remember having copy/pasted a decompiled version so I could use it. MS finally decided to make it public though. I suppose LINQKit has a custom version for the same reason, and this lets them support older framework versions. – Graviton