Expression Tree Practice: Beginner Basics

2019年12月15日 70点热度 0人点赞 3条评论
内容目录

Expression Tree Practice: Getting Started

[TOC]

img

What is an Expression Tree

Definition from Microsoft Official Documentation:

An expression tree represents code as a tree data structure.

What can it do?

You can edit and compute code in the expression tree. This makes it possible to dynamically modify executable code, execute LINQ queries across different databases, and create dynamic queries.

Is it fun?

Expression trees can also be used in the Dynamic Language Runtime (DLR) to provide interoperability between dynamic languages and the .NET Framework, while ensuring that compiler writers can emit expression trees instead of Microsoft Intermediate Language (MSIL).

Where is it applied?

ORM frameworks, workflow frameworks, etc., use code involving Lambdas... dynamic execution of code, dynamic assembly of code, and so on.

Creating Expression Trees

There are two ways to create expression trees: using lambda expressions and using APIs.

Creating an expression tree means that each node has been defined beforehand, and finally combining all nodes using code to generate the expression tree.

Example (Creating an expression tree using APIs)

```
            ParameterExpression a = Expression.Parameter(typeof(int), "i");
            ParameterExpression b = Expression.Parameter(typeof(int), "j");
        Expression r1 = Expression.Multiply(a, b);      // Multiplication
        ParameterExpression c = Expression.Parameter(typeof(int), "x");
        ParameterExpression d = Expression.Parameter(typeof(int), "y");
        Expression r2 = Expression.Multiply(c, d);      // Multiplication

        Expression result = Expression.Add(r1, r2);     // Addition
        // The above code generates nodes
        // Generate expression
        Expression<Func<int, int, int, int, int>> func = Expression.Lambda<Func<int, int, int, int, int>>(result, a, b, c, d);
        var com = func.Compile();
        Console.WriteLine("Expression: " + func);
        Console.WriteLine(com(12, 12, 13, 13));
        Console.ReadKey();</code></pre>

The code above regarding expression trees is lengthy, and the following step is called generating/creating the expression tree.

            Expression<Func<int, int, int, int, int>> func = Expression.Lambda<Func<int, int, int, int, int>>(result, a, b, c, d);

The following line is called executing the expression tree.

            var com = func.Compile();

The other code is used to generate expression tree nodes/logics.

Returning to the main topic, the two methods for creating expression trees.

Creating Expression Trees with Lambda

The above example of expression trees is used to generate

 ( i * j ) + ( x * y ) 

However, writing such a long piece of code for such a simple operation is impractical.

Using lambda, it can be written as follows:

           Expression<Func<int, int, int, int, int>> func = (i, j, x, y) => (i * j) + (x * y);

If using lambda to generate expression trees, lambda can only use single-line statements; control statements like if, for, etc. cannot be used.

Specifics about lambda expression trees will be explained in other articles later.

Creating Expression Trees via API

It’s that simple:

Expression<Func<int, int, int, int, int>> func = Expression.Lambda<Func<int, int, int, int, int>>(result, a, b, c, d);

On the left side, both methods look the same; the distinction lies in the right side of the equals sign.

Expression< TDelegate >

The final result of the examples above generates

Expression<Func<int, int, int, int, int>> func 

func is the variable for the expression tree.

We can understand the methods and properties that the expression tree possesses.

The type used to generate expression tree nodes is Expression.

Thus, the created expression tree func is of type Expression<TDelegate> .

Definition is as follows:

public sealed class Expression<TDelegate> : LambdaExpression

It possesses the following methods:

Definition

Method Description
Compile() Compiles the lambda expression described by the expression tree into executable code and generates a delegate representing the lambda expression.
Compile(Boolean) Compiles the lambda expression described by the expression tree into interpreted or compiled code and generates a delegate representing that lambda expression.
Compile(DebugInfoGenerator) Compiles the lambda into a method definition. (Inherited from LambdaExpression)
Update(Expression, IEnumerable) Creates a new expression similar to this expression, but using the provided children. If all children are the same, this expression is returned.
Accept(ExpressionVisitor) Dispatches to the specific Visit method for this node type. For example, MethodCallExpression calls VisitMethodCall.

Since Expression<TDelegate> inherits from LambdaExpression, many properties and methods can also be used.

LambdaExpression

Body Gets the body of the lambda expression.
CanReduce Indicates whether the node can be reduced to a simpler node. If true, Reduce() can be called to produce a simplified form.
Name Gets the name of the lambda expression.
NodeType Returns the node type of this Expression.
Parameters Gets the parameters of the lambda expression.
ReturnType Gets the return type of the lambda expression.
TailCall Gets a value indicating whether the lambda expression will be compiled using tail call optimization.
Type Gets the static type of the expression represented by this Expression.

Alright, consider the above as a small note for reference, currently not necessary, but will be gradually used later.

Parsing/Executing Expression Trees

After creating the expression tree, it needs to be executed.

Before that, you need to understand delegates Delegate, Func, Action, and their relationships.

Executing an expression tree is done as follows:

            Expression<Func<int, int, int, int, int>> func = Expression.Lambda<Func<int, int, int, int, int>>(result, a, b, c, d);
            var com = func.Compile();
            var runRasult = com(12, 12, 13, 13);

func is merely an expression tree. After constructing the expression tree, to “convert the expression tree to code”, the .Compile() method can be used to generate a delegate (like the above com).

For brevity, the above uses var, but it is actually like this:

            Func<int,int,int,int,int> com = func.Compile();

Four parameters, one return value.

var runRasult = com(12, 12, 13, 13);

In C#, there is syntactic sugar; the delegate can be written like this:

        Expression<Func<int, int, int, int, int>> func = Expression.Lambda<Func<int, int, int, int, int>>(result, a, b, c, d);
    int runRasult = func.Compile()(12, 12, 13, 13);</code></pre>

From now on, we'll write like this wherever compressed one-line code is possible, and there's no need to write it out in two lines.

For debugging and viewing expression trees in VS, refer to:

https://docs.microsoft.com/zh-cn/dotnet/csharp/programming-guide/concepts/expression-trees/debugging-expression-trees-in-visual-studio

Beginners need not be entangled in these; just understanding the content of this article and remembering the key points will suffice.

痴者工良

高级程序员劝退师

文章评论