What does it mean for a label target to receive a value?
Asked Answered
C

2

8

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?

  1. public static GotoExpression Return(LabelTarget target);
  2. public static GotoExpression Continue(LabelTarget target);
  3. public static GotoExpression Break(LabelTarget target)
Corrinnecorrival answered 17/12, 2014 at 13:55 Comment(3)
Definitely better; thanks!Episcopacy
Curious about this myself. It appears that the Return, Continue, and Break all return essentially the same GotoExpression with the different being the .Kind property. This property is described as "informational only"Oud
Thanks, @Grax. I guess we empathize with each other. It is confusing. You're describing an informational property named GotoExpressionKind, which is an enum, which is for informational purposes only. However, I am asking about the meaning of two things: (1) How do you return, continue to, or break to a label target? and (2) How can a label receive a value?Corrinnecorrival
B
7

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.

Blush answered 17/12, 2014 at 18:29 Comment(4)
What a beautiful explanation! I love @Grax's answer too, but I have to give it to you for the astounding clarity of language and structure. You answer turned the key in my mind. Just one thing, though: with the documentation not at all having much, how did you know all this? You know if you tell me that, you'll be teaching the man to fish, and all.Corrinnecorrival
At one point, I worked on a project where we looked at using Expressions extensively. There was a lot of trial & error. If you want a fun, masochistic, challenge: There's no built in short-cut to make a for-loop (Expression.Loop essentially makes a while loop). Try building one.Blush
I also found the first couple posts of this blog series helpful: blogs.msdn.com/b/mattwar/archive/2008/11/18/linq-links.aspxBlush
Thanks, @Shlomo. Over the last year or so, I've gone through the posts on The Wayward Weblog. I'm going to be banging out code for composing and visiting all the 73 families (I've been at work counting and categorizing) of expressions in the System.Linq.Expressions namespace soon. :-)Corrinnecorrival
O
1

The purpose of the label value appears to be to provide a return value. If you look at my sample code below, the return value of "payload" is passed to the "target" label and becomes the return code for the expression. I tried Expression.Return and Expression.Break and got the same results for either. Expression.Continue did not have the overload to pass a value to the Label.

var target = Expression.Label(typeof(string));
var debugPrint = typeof(Debug).GetMethod("Print", new Type[] { typeof(string) });

var expr = Expression.Block(typeof(string),
    new Expression[] {
        Expression.Call(debugPrint,Expression.Constant("Before")),
        Expression.Return(target,Expression.Constant("payload"),typeof(string)),
        //Expression.Break(target,Expression.Constant("payload")),
        Expression.Call(debugPrint,Expression.Constant("During")),
        Expression.Label(target,Expression.Constant("Default")),
    }
);

var result = Expression.Lambda<Func<string>>(expr).Compile()();

This expression is roughly equivalent to the method below.

string Demo()
{
    Debug.Print("Before");
    return "payload";
    Debug.Print("During");
    return "Default";
}

To address the second question: You always "go to" a label target. "Return", "Continue", and "Break" are styles of how you might "go to" the target. Return implies that the label target is at the end of the method and you pass it the return value. Continue and Break imply that the label target is participating in a loop.

Oud answered 17/12, 2014 at 15:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.