SoFunction
Updated on 2025-03-07

Implementation of expression tree of C# loop and loop control

C# provides the following loop types.

Loop type describe
while loop Repeat statements or statement groups when the given condition is true. It tests the conditions before executing the loop body.
for/foreach loop Execute a sequence of statements multiple times to simplify the code that manages loop variables.
do...while loop Except that it is testing conditions at the end of the loop body, other things are similar to the while statement.
Nested loops You can use one or more loops within a while, for, or do..while loop.

Of course, there are also the following statements for controlling loops

Control statements describe
break statement TerminationlooporswitchStatement, the program flow will continue to execute the next statement followed by loop or switch.
continue statement Causes the cycle to skip the remainder of the body and start the test condition immediately.

LabelTarget

LabelTarget is used to create loop tags.

Whether it is for or while, when writing loops, you need to make a judgment that breaks out of the loop. Sometimes a certain parameter needs to increase and decrease automatically and serve as a basis for judgment.

There is no special representation for /while in the C# expression tree, there is only one Loop inside. Take a look at the expression tree generated by Loop

.Lambda #Lambda1<`1[System.Int32]>() {
    .Block(System.Int32 $x) {
        $x = 0;
        .Loop  {
            .If ($x < 10) {
                $x++
            } .Else {
                .Break #Label1 { $x }
            }
        }
        .LabelTarget #Label1:
    }
}

To implement loop control, there are two types of expressions: break and contauine:

        public static GotoExpression Break(LabelTarget target, Type type);

        public static GotoExpression Break(LabelTarget target, Expression value);

        public static GotoExpression Break(LabelTarget target);

        public static GotoExpression Break(LabelTarget target, Expression value, Type type);
        public static GotoExpression Continue(LabelTarget target, Type type);

        public static GotoExpression Continue(LabelTarget target);

Therefore, to achieve loop control, you must use LabelTarget, otherwise there will be infinite loops.

The best way to understand LabelTarget is to do it by hand.

for / while loop

Used to create loops, including for and while, defined as follows

        public static LoopExpression Loop(Expression body, LabelTarget @break, LabelTarget @continue);
        
      .
        public static LoopExpression Loop(Expression body);
      
        public static LoopExpression Loop(Expression body, LabelTarget @break);

The loop in the expression tree has only Loop and no difference between for / while.

So, let’s understand Loop loops and LabelTarget step by step;

Infinite loop

                while (true)
                {
                    ("Infinite Loop");
                }

Then, the corresponding Loop overload is this

public static LoopExpression Loop(Expression body)

Written using an expression tree

            BlockExpression _block = (
                new ParameterExpression[] { },
                (null, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) }),("Infinite Loop") )
            );

            LoopExpression _loop = (_block);

            Expression&lt;Action&gt; lambda = &lt;Action&gt;(_loop);
            ()();

The easiest loop

If I want to use an expression tree to achieve the simplest loop as follows, how do I write it?

            while (true)
            {
                ("I'm executed once and end the loop");
                break;
            }

Expression tree writing

            LabelTarget _break = ();

            BlockExpression _block = (
               new ParameterExpression[] { },
               (null, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) }), ("I'm executed once and end the loop")), (_break));
            LoopExpression _loop = (_block, _break);

            Expression&lt;Action&gt; lambda = &lt;Action&gt;(_loop);
            ()();

            ();

The generated expression tree

.Lambda #Lambda1&lt;&gt;() {
    .Loop  {
        .Block() {
            .Call ("I'm executed once and end the loop");
            .Break #Label1 { }
        }
    }
    .LabelTarget #Label1:
}

First of all, be clear,()It can be empty inside. It is a mark that does not participate in passing parameters or operations. If there are ginseng or not, just keep it consistent before and after.

But the loop above is only once, you can change the tag above to tryLabelTarget _break = (typeof(int));, find the reason later.

Also, the () variables need to be consistent, otherwise they cannot be jumped out.

Try the code

            BlockExpression _block = (
               new ParameterExpression[] { },
               (null, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) }), ("I'm executed once and end the loop")), (()));
            LoopExpression _loop = (_block, ());

            Expression&lt;Action&gt; lambda = &lt;Action&gt;(_loop);
            ()();

            ();

() is used in it, Block() is a block, that is, {}.

If Block() is in the outermost layer, it is equivalent to a function; if it is embedded, it is equivalent to {};

But that's not true. . . The expression tree is not restored completely according to the C# syntax.

For the use of Block(), add more practice.

Multiple cycles

Write a loop statement that loops ten times

            for (int i = 0; i < 10; i++)
            {
                if (i < 10)
                {
                    (i);
                }
                else
                    break;
            }

Or use while to indicate

            int i = 0;
            while (true)
            {
                if (i < 10)
                {
                    (i);
                }
                else
                    break;
                i++;
            }

Written using an expression tree

            LabelTarget _break = (typeof(int));
            ParameterExpression a = (typeof(int), "a");

            BlockExpression _block = (new ParameterExpression[] { },
                
                (
                    (a, (10)),
                    (null, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(int) }), a),
                    (_break, a)
                ),
                (a)   // a++
                );


            LoopExpression _loop = (_block, _break);

            Expression<Action<int>> lambda = <Action<int>>(_loop, a);
            ()(0);
            ();

The generated expression tree is as follows

.Lambda #Lambda1<`1[System.Int32]>(System.Int32 $a) {
    .Loop  {
        .Block() {
            .If ($a < 10) {
                .Call ($a)
            } .Else {
                .Break #Label1 { $a }
            };
            $a++
        }
    }
    .LabelTarget #Label1:
}

Try to(_break, a)Change to(_break). See what's wrong. . .

The solution is to change the above mark toLabelTarget _break = ();

Just like you write code and comments, the things inside are for easy understanding for others to read the code.

Some students are confused about(with or without ginseng);(_break, a)and(_break), just look at the final generated expression tree.

break and continue together

The C# loop code is as follows

            int i = 0;
            while (true)
            {
                if (i &lt; 10)
                {
                    if (i % 2 == 0)
                    {
                        ("i is an even number:");
                        (i);
                        i++;
                        continue;
                    }
                    ("Other tasks --");
                    ("Other tasks --");
                }
                else break;
                i++;
            }

Written using C# expression tree (the author splits the steps in detail, so the code is relatively long)

            ParameterExpression a = (typeof(int), "a");

            LabelTarget _break = ();
            LabelTarget _continue = ();

            //        if (i % 2 == 0)
            //        {
            // ("i is an even number:");            //            (i);
            //            i++;
            //            continue;
            //        }
            ConditionalExpression _if = (
                ((a, (2)), (0)),
                (
                    new ParameterExpression[] { },
                    (null, typeof(Console).GetMethod("Write", new Type[] { typeof(string) }), ("i is an even number:")),
                    (null, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(int) }), a),
                    (a),
                    (_continue)
                    )
                );

            //        if (i % 2 == 0)
            //        {
            // ("i is an even number:");            //            (i);
            //            i++;
            //            continue;
            //        }
            // ("Other tasks --");            // ("Other tasks --");            BlockExpression block1 = (
                new ParameterExpression[] { },
                _if,
                (null, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) }), ("Other tasks --")),
                (null, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) }), ("Other tasks --"))
                );

            //    if (i &lt; 10)
            //    {
            //        if (i % 2 == 0)
            //        {
            // ("i is an even number:");            //            (i);
            //            i++;
            //            continue;
            //        }
            // ("Other tasks --");            // ("Other tasks --");            //    }
            //    else break;
            ConditionalExpression if_else = (
               (a, (10)),
                block1,
                (_break)
                );


            //    if (i &lt; 10)
            //    {
            //        if (i % 2 == 0)
            //        {
            // ("i is an even number:");            //            (i);
            //            i++;
            //            continue;
            //        }
            // ("Other tasks --");            // ("Other tasks --");            //    }
            //    else break;
            //    i++ ;

            BlockExpression block2 = (
                new ParameterExpression[] { },
                if_else,
                (a)
                );
            // while(true)
            LoopExpression loop = (block2, _break, _continue);

            Expression&lt;Action&lt;int&gt;&gt; lambda = &lt;Action&lt;int&gt;&gt;(loop, a);
            ()(0);
            ();

The generated expression tree is as follows

.Lambda #Lambda1&lt;`1[System.Int32]&gt;(System.Int32 $a) {
    .Loop .LabelTarget #Label1: {
        .Block() {
            .If ($a &lt; 10) {
                .Block() {
                    .If (
                        $a % 2 == 0
                    ) {
                        .Block() {
                            .Call ("i is an even number:");
                            .Call ($a);
                            $a++;
                            .Continue #Label1 { }
                        }
                    } .Else {
                        .Default()
                    };
                    .Call ("Other tasks --");
                    .Call ("Other tasks --")
                }
            } .Else {
                .Break #Label2 { }
            };
            $a++
        }
    }
    .LabelTarget #Label2:
}

For the sake of ease of understanding, the above code splits many steps.

Let's have a simplified version

            ParameterExpression a = (typeof(int), "a");

            LabelTarget _break = ();
            LabelTarget _continue = ();

            LoopExpression loop = (
                (
                    new ParameterExpression[] { },
                    (
                        (a, (10)),
                        (
                            new ParameterExpression[] { },
                            (
                                ((a, (2)), (0)),
                                (
                                    new ParameterExpression[] { },
                                    (null, typeof(Console).GetMethod("Write", new Type[] { typeof(string) }), ("i is an even number:")),
                                    (null, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(int) }), a),
                                    (a),
                                    (_continue)
                                    )
                                ),
                            (null, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) }), ("Other tasks --")),
                            (null, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) }), ("Other tasks --"))
                            ),
                        (_break)
                        ),
                    (a)
                    ),
                _break,
                _continue
                );

            Expression&lt;Action&lt;int&gt;&gt; lambda = &lt;Action&lt;int&gt;&gt;(loop, a);
            ()(0);
            ();

It should be noted that There are differences.

When tag instantiation is()hour,

(label);
(label);

The difference is that continu can only be used ().

Break can do this

LabelTarget label =  ( typeof ( int ) );
ParameterExpression a = (typeof(int), "a");

 ( label , a ) 

The above is all the content of this article. I hope it will be helpful to everyone's study and I hope everyone will support me more.