Home .NET ExpressionTrees in C# by Example of Finding Derivative (Expression Tree Visitor vs Pattern matching)

ExpressionTrees in C# by Example of Finding Derivative (Expression Tree Visitor vs Pattern matching)

by admin

Good afternoon.Expression trees, especially in combination with the Visitor pattern, have always been a rather confusing topic. So, the more diverse information on this topic, the more examples, the easier it will be for interested people to find something that is understandable and useful to them.
ExpressionTrees in C# by Example of Finding Derivative (Expression Tree Visitor vs Pattern matching)
The article is structured as usual — starting with conceptual foundations and definitions and ending with examples and uses. The table of contents is below.
Basics of expression trees
Expression tree syntax
Types of expressions
Pattern Matching
Naive Visitor
Classic Visitor
Well and the goal is not to impose a certain solution or say that one is better than the other. I suggest that you draw your own conclusions, taking into account all the nuances in your case. I will give my opinion by my own example.

Expression Trees

Basics

First we need to understand expression trees. By them we mean the Expression type or any of its descendants (we’ll talk about them later). In the usual case, expressions/algorithms are represented as executable code/instructions, with which the user can do not so much (mostly execute). Expression type allows an expression/algorithm (usually lambdas, but not necessarily) to be represented as data organized in a tree structure to which the user has access. The tree-like way of organizing information about the algorithm and the name of the class is what gives us "expression trees".
For clarity, let’s look at a simple example. Suppose we have a lambda
(x) => Console.WriteLine(x + 5)
This can be represented as the following tree
ExpressionTrees in C# by Example of Finding Derivative (Expression Tree Visitor vs Pattern matching)
The root of the tree is the top " MethodCall ", method parameters are also expressions, hence can have any number of descendants.
In our case the descendant is one – the vertex " ArithmeticOperation ". It contains information about what operation exactly it is and the left and right operands are also expressions. Such a node will always have 2 descendants.
The operands are represented by a constant ( Constant ) and a parameter ( Parameter ). These expressions have no descendants.
These are very simplified examples, but fully capture the essence.
The main feature of expression trees is that you can parse them and read out all the necessary information about what the algorithm is supposed to do. From some point of view, this is the opposite of attributes. Attributes are a means of declaratively describing behavior (very conventionally, but the end goal is roughly that). Whereas expression trees are the use of a function/algorithm to describe data.
They are used, for example, in providers entity framework. The application is obvious – to parse the expression tree, understand what should be executed there, and use this description to compose SQL Some lesser known examples are the library for mocking moq Expression trees have also found use in DLR (dynamic language runtime). Compiler developers use them when providing compatibility between dynamic nature and dotnet, instead of generating MSIL
It’s also worth mentioning that expression trees are immutable.

Syntax

The next thing worth talking about is the syntax. There are 2 basic ways to :

  • Creation of expression trees through static methods of Expression class
  • Using lambda expressions, compiled into Expression

Static methods of the Expression class

Creating expression trees through static methods of the Expression class is used less frequently (especially from a user perspective). It’s cumbersome, but fairly simple, in our
have a lot of basic bricks at our disposal, from which we can build quite complex
things. Creation is done through static methods, because expression constructors have the modifier internal And it doesn’t mean that you have to uncheck reflexion.
As an example I give the creation of the expression from the example above :
(x) => Console.WriteLine(x + 5)

ParameterExpression parameter = Expression.Parameter(typeof(double));ConstantExpression constant = Expression.Constant(5d, typeof(double));BinaryExpression add = Expression.Add(parameter, constant);MethodInfo writeLine = typeof(Console).GetMethod(nameof(Console.WriteLine), new[] { typeof(double) });MethodCallExpression methodCall = Expression.Call(null, writeLine, add);Expression<Action<double> > expressionlambda = Expression.Lambda<Action<double> > (methodCall, parameter);Action<double> delegateLambda = expressionlambda.Compile();delegateLambda(123321);

This may not be a very convenient way, but it perfectly reflects the internal structure of expression trees. Plus it gives you more possibilities and features you can use in expression trees: from loops, conditions, try-catch, goto, assignment, fault blocks, debugging information for breakpoints, dynamic, etc.

Lambda expressions

Using lambda as expressions is a more common way. It works very simply, a smart compiler looks at what a lambda is used as at compile time. And compiles it either to a delegate or to an expression. On the example we’ve already learned, it looks like this

Expression<Action<double> > write = x => Console.WriteLine(x + 5);

It’s worth clarifying such a thing – an expression is an exhaustive description. And it is sufficient to
to get a result. The trees of expressions like LambdaExpression or its successors can be
converted to executable IL. The other types cannot be directly converted into executable code (but it doesn’t make much sense).
By the way, if someone needs a fast compilation of an expression, you can have a look at this third-party project.
The converse is generally not true. A delegate can’t just be represented by an expression (but it’s still possible).
Not all lambda can be converted into expression trees. These include :

  • Containing an assignment operator
  • Containing dynamic
  • Asynchronous
  • C body (curly braces)

double variable;dynamic dynamic;Expression<Action> assignment = () => variable = 5; //Compiler error: An expression tree may not contain an assignment operatorExpression<Func<double> > dynamically = () => dynamic; //Compiler error: An expression tree may not contain a dynamic operationExpression<Func<Task> > asynchon = async () => await Task.CompletedTask; //Compiler error: Async lambda cannot be converted to expresiion treesExpression<Action> body = () => { }; //Compiler error: A lambda expression with a statement body cannot be converted to an expression tree

Types of expressions

I suggest that we take a quick look at the types available so that we can get an idea of the possibilities we have. They are all in the namespace System.Linq.Expressions.
I suggest that you start with some really interesting and unusual possibilities. I’ve compiled more simple types of expressions into a table with short descriptions.

Dynamic

With DynamicExpression you can use dynamic and all its features in expression trees. It’s got a pretty confusing API, I sat on this example longer than all the others put together. All the confusion is provided by a bunch of different kinds of flags. And some of them are similar to what you’re looking for, but they’re not necessarily them. And when working with dynamic in expression trees, it’s hard to get a telling error. Example :

var parameter1 = Expression.Parameter(typeof(object), "name1");var parameter2 = Expression.Parameter(typeof(object), "name2");var dynamicParam1 = CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null);var dynamicParam2 = CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null);CallSiteBinder csb = Microsoft.CSharp.RuntimeBinder.Binder.BinaryOperation(CSharpBinderFlags.None, ExpressionType.Add, typeof(Program), new[] { dynamicParam1, dynamicParam2 });var dyno = Expression.Dynamic(csb, typeof(object), parameter1, parameter2);Expression<Func<dynamic, dynamic, dynamic> > expr = Expression.Lambda<Func<dynamic, dynamic, dynamic> > (dyno, new[] { parameter1, parameter2 });Func<dynamic, dynamic, dynamic> action = expr.Compile();var res = action("1", "2");Console.WriteLine(res); //12res = action(1, 2);Console.WriteLine(res); //3

I explicitly indicated where the Binder comes from to avoid confusion with the binder from System.Reflection. The interesting thing is that we can do ref and out parameters, named parameters, unary operations and basically anything you can do with dynamic, but it takes some skill.

Exception handling blocks

The second thing I’ll point out is the try/catch/finally/fault functionality, to be exact,
That we have access to the fault block. It’s not available in C#, but it is in MSIL.
analog of finally, which will be thrown in case of any exception. In the example below an exception will be thrown, then "Hi" will be thrown and the program will wait for input. Only then will it finally crash. I do not recommend this practice for use.

var throwSmth = Expression.Throw(Expression.Constant(new Exception(), typeof(Exception)));var log = Expression.Call(null, typeof(Console).GetMethod(nameof(Console.WriteLine), new[] { typeof(string) }), Expression.Constant("Hi", typeof(string)));var read = Expression.Call(null, typeof(Console).GetMethod(nameof(Console.ReadLine)));var fault = Expression.TryFault(throwSmth, Expression.Block(new[] { log, read }));Expression<Action> expr = Expression.Lambda<Action> (fault);Action compiledExpression = expr.Compile();compiledExpression();

A brief description of the available expression tree types

Table

Type Short description
Basic
Expression An abstract type that is basic to the other expression types. It also contains useful static methods, through which the necessary types of expressions are created
Expression<TDelegate> Represents the expression tree for the lambda
Simple actions
BinaryExpression Expression type for binary operators (+, -, etc.)
UnaryExpression Unary operators(+, -) as well as throw expression
ConstantExpression A constant is just an instance of
ParameterExpression Sets the parameter
MethodCallExpression Represents a method call, takes MethodInfo
IndexExpression Indexer
BlockExpression A block containing a sequence of expressions.A variable can be declared in it
Execution flow control
ConditionalExpression Conditional Expression – if-else
LabelTarget Label for goto jumps
LabelExpression A label expression that can be placed in the structure of an expression tree for later jumping to it.It is created with a LabelTarget.If it is jumped on, it gets the value of the corresponding GotoExpression, otherwise it gets the default value.Can be used with void, then there will be no assignment.
GotoExpression An unconditional jump.Can be of different types. (Including "break").
LoopExpression Infinite loop, exit with "break"
SwitchCase Used to represent SwitchExpressionitems
SwitchExpression The switch/caseitself
TryExpression Expression type for try/catch/finally/fault block representation
CatchBlock A type that is not an expression but contains them inside
Initialization
ElementInit Initializer for a single element in an IEnumerable. Used in ListInitExpression.
ListInitExpression Constructor call + initialization of a collection of elements
DefaultExpression Default value for a type or empty expression
NewArrayExpression Initialize new array + initialize elements
NewExpression Call of the constructor
Actions with fields/properties
MemberAssignment Assignment to a field or a property of an object
MemberBinding Provides a base class from which classes representing the bindings are inherited that are used to initialize the members of the newly created object
MemberExpression Access to field/property
MemberInitExpression Calling the constructor and initializing the fields of the created object
MemberListBinding Initialization of a property/field of a collection type
MemberMemberBinding Initialization of a field/property of an object which is itself a field/property
Other
LambdaExpression Lambda
InvocationExpression Applies a delegate or lambda expression to a list of argument expressions
DebugInfoExpression Sets or clears a breakpoint for debugging information. This allows the debugger to highlight the correct source code when debugging
SymbolDocumentInfo Stores debugging symbol information for the source file, in particular the file name and unique language identifier.
DynamicExpression Represents a dynamic operation (discussed above)
RuntimeVariablesExpression Expression representing read/write access to runtime variables
TypeBinaryExpression Checks if the object is of this type (is)

Also in this namespace there is

  • ExpressionVisitor- gives a viewer for expression trees. We’ll look at it later.
  • DynamicExpressionVisitor – made with DynamicExpression in mind (VisitDynamic method)

This information is enough to start comparing methods of working with expression trees. I decided to break it all down by the example of finding a derivative. I did not consider all possible variants, just the basic ones. But if someone decided to refine and use it for some reason – I’d be happy if you could share your improvements via a request to my repository

Pattern matching (pattern matching)

So, the problem is to make a tulu-calculus of derivatives. We can figure out the following: there are a couple of rules for finding the derivative for different types of operations – multiplication, division, etc. Depending on the operation, you have to choose a certain formula. In this trivial formulation, the problem is ideally suited to switch/case And in the latest version of the language we are presented with switch/case 2.0 or pattern matching
It’s hard to discuss anything here. On hubra this much code looks cumbersome and hard to read, so I suggest looking at github On the derivation example it came out like this :
Example

public class PatterntMatchingDerivative{private readonly MethodInfo _pow = typeof(Math).GetMethod(nameof(Math.Pow));private readonly MethodInfo _log = typeof(Math).GetMethod(nameof(Math.Log), new[] { typeof(double) });private readonly ConstantExpression _zero = Expression.Constant(0d, typeof(double));private readonly ConstantExpression _one = Expression.Constant(1d, typeof(double));public Expression<Func<double, double> > ParseDerivative(Expression<Func<double, double> > function){return Expression.Lambda<Func<double, double> > (ParseDerivative(function.Body), function.Parameters);}private Expression ParseDerivative(Expression function) => function switch{BinaryExpression binaryExpr => function.NodeType switch{ExpressionType.Add => Expression.Add(ParseDerivative(binaryExpr.Left), ParseDerivative(binaryExpr.Right)), ExpressionType.Subtract => Expression.Subtract(ParseDerivative(binaryExpr.Left), ParseDerivative(binaryExpr.Right)), ExpressionType.Multiply => (binaryExpr.Left, binaryExpr.Right) switch{(ConstantExpression _, ConstantExpression _) => _zero, (ConstantExpression constant, ParameterExpression _) => constant, (ParameterExpression _, ConstantExpression constant) => constant, _ => Expression.Add(Expression.Multiply(ParseDerivative(binaryExpr.Left), binaryExpr.Right), Expression.Multiply(binaryExpr.Left, ParseDerivative(binaryExpr.Right)))}, ExpressionType.Divide => (binaryExpr.Left, binaryExpr.Right) switch{(ConstantExpression _, ConstantExpression _) => _zero, (ConstantExpression constant, ParameterExpression parameter) => Expression.Divide(constant, Expression.Multiply(parameter, parameter)), (ParameterExpression _, ConstantExpression constant) => Expression.Divide(_one, constant), _ => Expression.Divide(Expression.Subtract(Expression.Multiply(ParseDerivative(binaryExpr.Left), binaryExpr.Right), Expression.Multiply(binaryExpr.Left, ParseDerivative(binaryExpr.Right))), Expression.Multiply(binaryExpr.Right, binaryExpr.Right))}, }, MethodCallExpression methodCall when methodCall.Method == _pow => (methodCall.Arguments[0], methodCall.Arguments[1]) switch{(ConstantExpression constant, ParameterExpression _) => Expression.Multiply(methodCall, Expression.Call(null, _log, constant)), (ParameterExpression param, ConstantExpression constant) => Expression.Multiply(constant, Expression.Call(null, _pow, param, Expression.Constant((double)constant.Value - 1, typeof(double)))), (ConstantExpression constant, Expression expression) => Expression.Multiply(Expression.Multiply(ParseDerivative(expression), methodCall), Expression.Call(null, _log, constant)), }, _ => function.NodeType switch{ExpressionType.Constant => _zero, ExpressionType.Parameter => _one, _ => throw new OutOfMemoryException("Bitmap best practice")}};}

It looks a little offbeat, but it’s interesting. It was a pleasure to write this – all the terms fit seamlessly into one line.
The example speaks for itself, words can’t describe it better.

Naive visitor

In a problem like this, the word tree visitor immediately comes to mind, which makes a lot of noise
and a little bit of panic among those who like to discuss adjail in the kitchen. "Fear not ignorance, but false knowledge. It is better to know nothing than to take as true what is not true." Remembering this wonderful phrase of Tolstoy, admitting ignorance, and enlisting the support of Google, one can find the following guide
I have this link as my first (after Siberia in 1949) for "Expression tree visitor".
At first glance, this is exactly the right one. The title of the article fits what we want to do, and the classes in the examples are named with the suffix Visitor
After reading the article and making an analogy for our example with derivatives we get :
Link to github
Example

public class CustomDerivativeExpressionTreeVisitor{public Expression<Func<double, double> > Visit(Expression<Func<double, double> > function){return Expression.Lambda<Func<double, double> > (Visitor.CreateFromExpression(function.Body).Visit(), function.Parameters);}}public abstract class Visitor{protected static readonly MethodInfo Pow = typeof(Math).GetMethod(nameof(Math.Pow));protected static readonly MethodInfo Log = typeof(Math).GetMethod(nameof(Math.Log), new[] { typeof(double) });protected readonly ConstantExpression Zero = Expression.Constant(0d, typeof(double));protected readonly ConstantExpression One = Expression.Constant(1d, typeof(double));public abstract Expression Visit();public static VisitorCreateFromExpression(Expression node)=> node switch{BinaryExpression be => new BinaryVisitor(be), MethodCallExpression mce when mce.Method == Pow => new PowMethodCallVisitor(mce), _ => new SimpleVisitor(node), };}public class BinaryVisitor : Visitor{private readonly BinaryExpression _node;public BinaryVisitor(BinaryExpression node){_node = node;}public override Expression Visit()=> _node.NodeType switch{ExpressionType.Add => Expression.Add(ParseDerivative(binaryExpr.Left), ParseDerivative(binaryExpr.Right)), ExpressionType.Subtract => Expression.Subtract(ParseDerivative(binaryExpr.Left), ParseDerivative(binaryExpr.Right)), ExpressionType.Multiply => (binaryExpr.Left, binaryExpr.Right) switch{(ConstantExpression _, ConstantExpression _) => _zero, (ConstantExpression constant, ParameterExpression _) => constant, (ParameterExpression _, ConstantExpression constant) => constant, _ => Expression.Add(Expression.Multiply(ParseDerivative(binaryExpr.Left), binaryExpr.Right), Expression.Multiply(binaryExpr.Left, ParseDerivative(binaryExpr.Right)))}, ExpressionType.Divide => (binaryExpr.Left, binaryExpr.Right) switch{(ConstantExpression _, ConstantExpression _) => _zero, (ConstantExpression constant, ParameterExpression parameter) => Expression.Divide(constant, Expression.Multiply(parameter, parameter)), (ParameterExpression _, ConstantExpression constant) => Expression.Divide(_one, constant), _ => Expression.Divide(Expression.Subtract(Expression.Multiply(ParseDerivative(binaryExpr.Left), binaryExpr.Right), Expression.Multiply(binaryExpr.Left, ParseDerivative(binaryExpr.Right))), Expression.Multiply(binaryExpr.Right, binaryExpr.Right))}, };}public class PowMethodCallVisitor : Visitor{private readonly MethodCallExpression _node;public PowMethodCallVisitor(MethodCallExpression node){_node = node;}public override Expression Visit()=> (_node.Arguments[0], _node.Arguments[1]) switch{(ConstantExpression constant, ParameterExpression _) => Expression.Multiply(_node, Expression.Call(null, Log, constant)), (ParameterExpression param, ConstantExpression constant) => Expression.Multiply(constant, Expression.Call(null, Pow, param, Expression.Constant((double)constant.Value - 1, typeof(double)))), (ConstantExpression constant, Expression expression) => Expression.Multiply(Expression.Multiply(CreateFromExpression(expression).Visit(), _node), Expression.Call(null, Log, constant)), };}public class SimpleVisitor : Visitor{private readonly Expression _node;public SimpleVisitor(Expression node){_node = node;}public override Expression Visit()=> _node.NodeType switch{ExpressionType.Constant => Zero, ExpressionType.Parameter => One, };}

Basically, we smeared the switch cases into different classes. There’s no less of them, no more magic. Still the same cases, many more lines. Where is the promised double double dispatch dispatch?

Classic visitor and double dispatch

Here we should already talk about the pattern itself " Visitor " aka Visitor which is the basis of the Expression tree visitor ‘a. Let’s take it as an example of expression trees.
Let’s assume for a second that we are developing expression trees. We want to let users iterate through the expression tree and, depending on the node types (Expression types), do certain actions.
First version – do nothing. That is, make users use switch/case. It is not such a bad way. But here we have such a nuance: we smudge the logic responsible for a particular type. Simply put, polymorphism and virtual calls ( aka late binding ) allow us to transfer the type definition to the execution environment and remove these checks from our code. We only need to have logic that creates an instance of the type we want, then the runtime will do everything for us.
Second option. The obvious solution is to put the logic into virtual methods. By redefining a virtual method in each descendant we can forget about switch/case. The polymorphic call mechanism will solve it for us. The method table will work here, the methods will be called according to the offset in it. But this is a topic for a whole article, so let’s not get carried away. It seems that virtual methods solve our problem. But unfortunately, they create another one. For our problem we could have added the GetDeriviative() method. But now the expression classes themselves look weird. We could add such methods for all occasions, but they do not fit into the general class logic. And we still haven’t given users the ability to do something like that (in an adequate way, of course). We need to let the user define the logic for each specific type, but keep the polymorphism (which is available to us).
The user’s efforts alone will not do this.
This is where the real visitor lies. In the base type of the hierarchy (Expression in our case) we define a method of the form

virtual Expression Accept(ExpressionVisitor visitor);

This method will be overridden in the descendants.
ExpressionVisitor itself is a base class containing a virtual method with the same signature
for each type of hierarchy. Using the ExpressionVisitor class as an example – VisitBinary(…), VisitMethodCall(…), VisitConstant(…), VisitParameter(…).
These methods are called in the corresponding class of our hierarchy.
That is, the Accept method in the BinaryExpression class will look like this :

protected internal override Expression Accept(ExpressionVisitor visitor){return visitor.VisitBinary(this);}

In the end, in order to define a new behavior, the user only needs to create a descendant of the ExpressionVisitor class in which the corresponding methods are overridden to solve the same problem. In our case, DerivativeExpressionVisitor is created.
Then we have some Expression descendant objects, but which ones we don’t know, but we don’t need to.
We call the Accept virtual method with the ExpressionVisitor implementation we need, i.e. DerivativeExpressionVisitor. Thanks to dynamic dispatch, an overridden Accept implementation of the runtime type, say BinaryExpression, is called. In the body of this method, we know perfectly well that we’re in BinaryExpression, but we don’t know which ExpressionVisitor descendant came to us. But since VisitBinary is also virtual, we don’t need to know. Again we just call the reference to the base class, the call is dynamically (at runtime) dispatched and the overridden implementation of the VisitBinary runtime type is called. So much for double dispatch – ping pong in the "you execute" – "no, you do" style.
What this gives us. In fact, it gives us the ability to "add" virtual methods from the outside, without
changing the class. Sounds great, but it has its downsides :

  1. Some kind of leftist Accept method, which is responsible for everything and nothing at the same time
  2. The ripple effect of a good hash function – when adding just one heir to a hierarchy, in the worst case everyone has to finish their visitors

But the nature of expression trees allows these costs due to the specifics of working with expressions, because this kind of bypassing is one of their main features.
Here you can see all the methods available for overload.
So, let’s see what it looks like in the end.
Link to github.
Example

public class BuildinExpressionTreeVisitor : ExpressionVisitor{private readonly MethodInfo _pow = typeof(Math).GetMethod(nameof(Math.Pow));private readonly MethodInfo _log = typeof(Math).GetMethod(nameof(Math.Log), new[] { typeof(double) });private readonly ConstantExpression _zero = Expression.Constant(0d, typeof(double));private readonly ConstantExpression _one = Expression.Constant(1d, typeof(double));public Expression<Func<double, double> > GetDerivative(Expression<Func<double, double> > function){return Expression.Lambda<Func<double, double> > (Visit(function.Body), function.Parameters);}protected override Expression VisitBinary(BinaryExpression binaryExpr)=> binaryExpr.NodeType switch{ExpressionType.Add => Expression.Add(Visit(binaryExpr.Left), Visit(binaryExpr.Right)), ExpressionType.Subtract => Expression.Subtract(Visit(binaryExpr.Left), Visit(binaryExpr.Right)), ExpressionType.Multiply when binaryExpr.Left is ConstantExpression binaryExpr.Right is ConstantExpression => _zero, ExpressionType.Multiply when binaryExpr.Left is ConstantExpression binaryExpr.Right is ParameterExpression => binaryExpr.Left, ExpressionType.Multiply when binaryExpr.Left is ParameterExpression binaryExpr.Right is ConstantExpression => binaryExpr.Right, ExpressionType.Multiply => Expression.Add(Expression.Multiply(Visit(binaryExpr.Left), binaryExpr.Right), Expression.Multiply(binaryExpr.Left, Visit(binaryExpr.Right))), ExpressionType.Divide when binaryExpr.Left is ConstantExpression binaryExpr.Right is ConstantExpression => _zero, ExpressionType.Divide when binaryExpr.Left is ConstantExpression binaryExpr.Right is ParameterExpression => Expression.Divide(binaryExpr.Left, Expression.Multiply(binaryExpr.Right, binaryExpr.Right)), ExpressionType.Divide when binaryExpr.Left is ParameterExpression binaryExpr.Right is ConstantExpression => Expression.Divide(_one, binaryExpr.Right), ExpressionType.Divide => Expression.Divide(Expression.Subtract(Expression.Multiply(Visit(binaryExpr.Left), binaryExpr.Right), Expression.Multiply(binaryExpr.Left, Visit(binaryExpr.Right))), Expression.Multiply(binaryExpr.Right, binaryExpr.Right)), };protected override Expression VisitMethodCall(MethodCallExpression methodCall)=> (methodCall.Arguments[0], methodCall.Arguments[1]) switch{(ConstantExpression constant, ParameterExpression _) => Expression.Multiply(methodCall, Expression.Call(null, _log, constant)), (ParameterExpression param, ConstantExpression constant) => Expression.Multiply(constant, Expression.Call(null, _pow, param, Expression.Constant((double)constant.Value - 1, typeof(double)))), (ConstantExpression constant, Expression expression) => Expression.Multiply(Expression.Multiply(Visit(expression), methodCall), Expression.Call(null, _log, constant)), };protected override Expression VisitConstant(ConstantExpression _) => _zero;protected override Expression VisitParameter(ParameterExpression b) => _one;}

Conclusions

Perhaps, as with most programming problems, no single answer can be given. As always, it all depends on the specific situation. I like the usual pattern-matching for my example, since I haven’t developed it to the scale of industrial development. In the case of this expression increasing uncontrollably, it would be worth thinking about a visitor. And even the naive visitor has a right to life, because it’s not a bad way to disperse a large amount of code into classes, if the hierarchy has not provided support from its side. And even here there are exceptions.
Likewise, the hierarchy’s very support of the Visitor is a very controversial thing.
But I hope that the information provided here is enough to make the right choice.

You may also like