I have a few questions about the System.Linq.Expressions.LabelExpression
and its ancillary classes and methods.
1) The documentation for the LabelExpression
class reads thus:
Represents a label, which can be put in any Expression context. If it is jumped to, it will get the value provided by the corresponding GotoExpression
. Otherwise, it receives the value in DefaultValue
. If the Type
equals System.Void
, no value should be provided.
What does it mean to return a value to a label target? In other words, what does it mean for a label target to receive a value? I've never done that in my life -- pass a value to a label target when I jump to it?
2) While it perfectly makes sense to go to a label target, what does it mean to return to and continue to and break to a label target?
public static GotoExpression Return(LabelTarget target);
public static GotoExpression Continue(LabelTarget target);
public static GotoExpression Break(LabelTarget target)
It is sometimes helpful to think of Linq Expressions as a way to build code in something that resembles C#, but isn't C# exactly. This is one of those times.
The below code is an implementation of a Math.Max(int a, int b)
using Expressions. There is no shortcut for the return
statements like in C#. You have to create labels.
// (a, b =>
// {
// if(a > b)
// return a;
// else
// return b;
// }
var a = Expression.Parameter(typeof(int), "a");
var b = Expression.Parameter(typeof(int), "b");
var returnLabel = Expression.Label(typeof (int));
Expression<Func<int, int, int>> returnMax = (Expression<Func<int, int, int>>)Expression.Lambda
(
Expression.Block
(
Expression.IfThenElse
(
Expression.GreaterThan(a, b),
Expression.Return(returnLabel, a),
Expression.Return(returnLabel, b)
),
Expression.Label(returnLabel, Expression.Constant(0))
),
a,
b
);
var shouldBeSix = returnMax.Compile()(5, 6);
The key to understanding why the LabelExpression
needs a value: Expressions are always typed (for our purposes here, void
is a type), and almost always return a value. A BlockExpression
, for example, take on the value of the last statement. An AssignExpression
takes on the value of assignment. Similarly, a LabelExpression
must return a value. When used in conjunction with a GotoExpression
of any sort, that default value is never used, but the following code is legal:
var returnLabel = Expression.Label(typeof (int));
Expression<Func<int>> returnsSix = (Expression<Func<int>>)Expression.Lambda
(
Expression.Label(
returnLabel,
Expression.Constant(6)
)
);
var alsoSix = returnsSix.Compile()();
... hence the need for a default value.
Since a LabelExpression
must have a type, and a value, the types of the default value, the LabelTarget
and the GotoExpression
all must match. The original sample code uses 0 as a default value, but as you can see, that will never be used. If you switch the 0
for 0.0
or null
, the Expression will fail on the .Compile()
call.
2) As you can see from the sample code, there is no way to 'return' out of a function without using a label target. As @Grax implied, Expression.Goto
, Expression.Continue
, Expression.Break
, Expression.Return
all return GotoExpressions
that function almost identically.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With