Catalog
Exercise Practice of Expression Tree: C# Loop
C# provides the following types of loops.
Cycle type | describe |
---|---|
while cycle | When a given condition is true, repeat statements or statement groups. It tests the condition before executing the body of the loop. |
for/foreach loop | Execute a sequence of statements several times to simplify the code for managing loop variables. |
do...while loop | Except that it tests conditions at the end of the body of the loop, it is similar to the while statement. |
Nested loop | You can use one or more loops in while, for, or do. while loops. |
Of course, there are also the following statements for controlling loops
Control statement | describe |
---|---|
break statement | Terminate the loop or switch statement, and the program flow will continue to execute the next statement immediately following the loop or switch. |
continue statement | Causes the loop to skip the rest of the body and immediately restart the test conditions. |
LabelTarget
LabelTarget is used to create circular tags.
Whether for or while, when writing a cycle, we need to have a judgment of jumping out of the cycle. Sometimes, we need a parameter to increase or decrease itself as a judgment basis.
There is no special expression for /while in the C # expression tree. There is only one Loop in it. Look at the expression tree generated by Loop
.Lambda #Lambda1<System.Func`1[System.Int32]>() { .Block(System.Int32 $x) { $x = 0; .Loop { .If ($x < 10) { $x++ } .Else { .Break #Label1 { $x } } } .LabelTarget #Label1: } }
To achieve circular control, there are two 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, in order to achieve cycle control, Label Target must be used, otherwise it will be infinite cycle.
The best way to understand Label Target is to do it by hand.
for / while loop
Expression.Loop is used to create loops, including for and while, defined as follows
public static LoopExpression Loop(Expression body, LabelTarget @break, LabelTarget @continue); System.Linq.Expressions.LoopExpression. public static LoopExpression Loop(Expression body); public static LoopExpression Loop(Expression body, LabelTarget @break);
There is only Loop in the expression tree, no for / while difference.
So let's step by step understand Loop loops and Label Target.
Infinite cycle
while (true) { Console.WriteLine("Infinite cycle"); }
So, the corresponding Loop overload is this
public static LoopExpression Loop(Expression body)
Writing with expression tree
BlockExpression _block = Expression.Block( new ParameterExpression[] { }, Expression.Call(null, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) }),Expression.Constant("Infinite cycle") ) ); LoopExpression _loop = Expression.Loop(_block); Expression<Action> lambda = Expression.Lambda<Action>(_loop); lambda.Compile()();
The simplest cycle
If I want to use the expression tree to do the following simple loop, how to write it?
while (true) { Console.WriteLine("I was executed once and the loop ended."); break; }
Expression Tree Writing
LabelTarget _break = Expression.Label(); BlockExpression _block = Expression.Block( new ParameterExpression[] { }, Expression.Call(null, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) }), Expression.Constant("I was executed once and the loop ended.")), Expression.Break(_break)); LoopExpression _loop = Expression.Loop(_block, _break); Expression<Action> lambda = Expression.Lambda<Action>(_loop); lambda.Compile()(); Console.ReadKey();
Generated expression tree
.Lambda #Lambda1<System.Action>() { .Loop { .Block() { .Call System.Console.WriteLine("I was executed once and the loop ended."); .Break #Label1 { } } } .LabelTarget #Label1: }
First of all, it should be clear that the Expression.Label() can be empty, it is a marker, does not participate in passing parameters, does not participate in operations. If there is any participation or no participation, it is enough to keep consistency before and after.
But the loop above is only one time, you can change the label above to try LabelTarget_break = Expression. Label (typeof (int);, the reason will be found later.
Also, the Expression.Label() variable needs to be consistent, otherwise it cannot be jumped out.
Try the code.
BlockExpression _block = Expression.Block( new ParameterExpression[] { }, Expression.Call(null, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) }), Expression.Constant("I was executed once and the loop ended.")), Expression.Break(Expression.Label())); LoopExpression _loop = Expression.Loop(_block, Expression.Label()); Expression<Action> lambda = Expression.Lambda<Action>(_loop); lambda.Compile()(); Console.ReadKey();
It uses Expression.Block(), which is a block, namely {}.
If Block() is at the outermost level, it is a function; if it is embedded, it is {};
But that's not true... In the expression tree, operations are not restored in full accordance with the C # grammar.
For the use of Block(), more practice is enough.
Multiple cycles
Write a loop statement ten times
for (int i = 0; i < 10; i++) { if (i < 10) { Console.WriteLine(i); } else break; }
Or use the while representation
int i = 0; while (true) { if (i < 10) { Console.WriteLine(i); } else break; i++; }
Writing with expression tree
LabelTarget _break = Expression.Label(typeof(int)); ParameterExpression a = Expression.Variable(typeof(int), "a"); BlockExpression _block = Expression.Block(new ParameterExpression[] { }, Expression.IfThenElse ( Expression.LessThan(a, Expression.Constant(10)), Expression.Call(null, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(int) }), a), Expression.Break(_break, a) ), Expression.PostIncrementAssign(a) // a++ ); LoopExpression _loop = Expression.Loop(_block, _break); Expression<Action<int>> lambda = Expression.Lambda<Action<int>>(_loop, a); lambda.Compile()(0); Console.ReadKey();
The resulting expression tree is as follows
.Lambda #Lambda1<System.Action`1[System.Int32]>(System.Int32 $a) { .Loop { .Block() { .If ($a < 10) { .Call System.Console.WriteLine($a) } .Else { .Break #Label1 { $a } }; $a++ } } .LabelTarget #Label1: }
Try changing Expression.Break(_break, a) to Expression.Break(_break). Look what's wrong with the newspaper...
The solution is to change the label above to LabelTarget_break = Expression. Label ();.
Just like you write code and comment, what's inside is to make it easy for others to understand.
Some students are entangled with Express. Label (with or without parameters); Express. Break (_break, a) and Express. Break (_break), as long as you look at the final expression tree generated.
break and continue together
The C# loop code is as follows
int i = 0; while (true) { if (i < 10) { if (i % 2 == 0) { Console.Write("i Even numbers:"); Console.WriteLine(i); i++; continue; } Console.WriteLine("Other tasks --"); Console.WriteLine("Other tasks --"); } else break; i++; }
Write using C expression tree (I split the steps in detail, so the code is longer)
ParameterExpression a = Expression.Variable(typeof(int), "a"); LabelTarget _break = Expression.Label(); LabelTarget _continue = Expression.Label(); // if (i % 2 == 0) // { // Console.Write("i is even:"); // Console.WriteLine(i); // i++; // continue; // } ConditionalExpression _if = Expression.IfThen( Expression.Equal(Expression.Modulo(a, Expression.Constant(2)), Expression.Constant(0)), Expression.Block( new ParameterExpression[] { }, Expression.Call(null, typeof(Console).GetMethod("Write", new Type[] { typeof(string) }), Expression.Constant("i Even numbers:")), Expression.Call(null, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(int) }), a), Expression.PostIncrementAssign(a), Expression.Continue(_continue) ) ); // if (i % 2 == 0) // { // Console.Write("i is even:"); // Console.WriteLine(i); // i++; // continue; // } // Console.WriteLine("Other Tasks--"); // Console.WriteLine("Other Tasks--"); BlockExpression block1 = Expression.Block( new ParameterExpression[] { }, _if, Expression.Call(null, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) }), Expression.Constant("Other tasks --")), Expression.Call(null, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) }), Expression.Constant("Other tasks --")) ); // if (i < 10) // { // if (i % 2 == 0) // { // Console.Write("i is even:"); // Console.WriteLine(i); // i++; // continue; // } // Console.WriteLine("Other Tasks--"); // Console.WriteLine("Other Tasks--"); // } // else break; ConditionalExpression if_else = Expression.IfThenElse( Expression.LessThan(a, Expression.Constant(10)), block1, Expression.Break(_break) ); // if (i < 10) // { // if (i % 2 == 0) // { // Console.Write("i is even:"); // Console.WriteLine(i); // i++; // continue; // } // Console.WriteLine("Other Tasks--"); // Console.WriteLine("Other Tasks--"); // } // else break; // i++ ; BlockExpression block2 = Expression.Block( new ParameterExpression[] { }, if_else, Expression.PostIncrementAssign(a) ); // while(true) LoopExpression loop = Expression.Loop(block2, _break, _continue); Expression<Action<int>> lambda = Expression.Lambda<Action<int>>(loop, a); lambda.Compile()(0); Console.ReadKey();
The resulting expression tree is as follows
.Lambda #Lambda1<System.Action`1[System.Int32]>(System.Int32 $a) { .Loop .LabelTarget #Label1: { .Block() { .If ($a < 10) { .Block() { .If ( $a % 2 == 0 ) { .Block() { .Call System.Console.Write("i Even numbers:"); .Call System.Console.WriteLine($a); $a++; .Continue #Label1 { } } } .Else { .Default(System.Void) }; .Call System.Console.WriteLine("Other tasks --"); .Call System.Console.WriteLine("Other tasks --") } } .Else { .Break #Label2 { } }; $a++ } } .LabelTarget #Label2: }
For ease of understanding, the above code is split into many steps.
A simplified version
ParameterExpression a = Expression.Variable(typeof(int), "a"); LabelTarget _break = Expression.Label(); LabelTarget _continue = Expression.Label(); LoopExpression loop = Expression.Loop( Expression.Block( new ParameterExpression[] { }, Expression.IfThenElse( Expression.LessThan(a, Expression.Constant(10)), Expression.Block( new ParameterExpression[] { }, Expression.IfThen( Expression.Equal(Expression.Modulo(a, Expression.Constant(2)), Expression.Constant(0)), Expression.Block( new ParameterExpression[] { }, Expression.Call(null, typeof(Console).GetMethod("Write", new Type[] { typeof(string) }), Expression.Constant("i Even numbers:")), Expression.Call(null, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(int) }), a), Expression.PostIncrementAssign(a), Expression.Continue(_continue) ) ), Expression.Call(null, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) }), Expression.Constant("Other tasks --")), Expression.Call(null, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) }), Expression.Constant("Other tasks --")) ), Expression.Break(_break) ), Expression.PostIncrementAssign(a) ), _break, _continue ); Expression<Action<int>> lambda = Expression.Lambda<Action<int>>(loop, a); lambda.Compile()(0); Console.ReadKey();
It should be noted that Expression.Break Expression.Continue is different.
When all tag instantiations are Expression.Label(),
Expression.Break(label); Expression.Continu(label);
The difference is that continu e can only use Expression.Label().
Break can do this
LabelTarget label = Expression.Label ( typeof ( int ) ); ParameterExpression a = Expression.Variable(typeof(int), "a"); Expression.Break ( label , a )