【问题标题】:What does it mean for a label target to receive a value?标签目标接收值意味着什么?
【发布时间】:2014-12-17 13:55:05
【问题描述】:

我对@9​​87654324@ 及其辅助类和方法有一些疑问。

1) LabelExpression 类的文档是这样写的:

表示一个标签,可以放在任何 Expression 上下文中。如果跳转到,会得到对应的GotoExpression提供的值。否则,它将接收DefaultValue 中的值。如果Type 等于System.Void,则不应提供任何值。

将值返回给标签目标是什么意思?换句话说,标签目标接收值意味着什么?我这辈子从来没有这样做过——当我跳转到标签目标时将值传递给它?

2) 虽然前往一个标签目标非常有意义,但返回继续和中断到标签目标?

  1. public static GotoExpression Return(LabelTarget target);
  2. public static GotoExpression Continue(LabelTarget target);
  3. public static GotoExpression Break(LabelTarget target)

【问题讨论】:

  • 绝对更好;谢谢!
  • 我自己对此感到好奇。 Return、Continue 和 Break 似乎都返回本质上相同的 GotoExpression,不同的是 .Kind 属性。此属性被描述为“仅供参考”
  • 谢谢,@Grax。我想我们互相同情。这令人困惑。您正在描述一个名为 GotoExpressionKind 的信息属性,它是一个枚举,仅供参考。但是,我问的是两件事的含义:(1)您如何返回,继续或突破标签目标? (2) 标签如何接收值?

标签: c# .net linq expression-trees linq-expressions


【解决方案1】:

有时将 Linq 表达式视为一种在类似于 C# 但不完全是 C# 的东西中构建代码的方法会很有帮助。这是其中之一。

以下代码是使用表达式的Math.Max(int a, int b) 的实现。 return 语句没有像 C# 中那样的快捷方式。您必须创建标签。

        // (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);

理解为什么LabelExpression 需要一个值的关键:表达式总是有类型的(为了我们这里的目的,void 是一个类型),并且几乎总是返回一个值。例如,BlockExpression 采用最后一条语句的值。 AssignExpression 具有赋值的值。同样,LabelExpression 必须返回一个值。当与任何类型的 GotoExpression 结合使用时,从不使用该默认值,但以下代码是合法的:

        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()();

...因此需要一个默认值。

由于LabelExpression 必须有一个类型和一个值,所以默认值、LabelTargetGotoExpression 的类型都必须匹配。原始示例代码使用 0 作为默认值,但正如您所见,它永远不会被使用。如果您将0 切换为0.0null,则表达式将在.Compile() 调用中失败。

2) 从示例代码中可以看出,如果不使用标签目标,就无法从函数中“返回”。正如@Grax 暗示的那样,Expression.GotoExpression.ContinueExpression.BreakExpression.Return 都返回GotoExpressions,该功能几乎相同。

【讨论】:

  • 多么美妙的解释!我也喜欢@Grax 的答案,但我必须把它交给你,因为语言和结构的清晰度令人震惊。你的回答变成了我心中的钥匙。不过,只有一件事:文档没有太多内容,您是如何知道这一切的?你知道,如果你告诉我,你就是在教这个人钓鱼等等。
  • 有一次,我参与了一个我们广泛使用表达式的项目。有很多试验和错误。如果你想要一个有趣的、自虐的、挑战:没有内置的捷径来制作一个 for 循环(Expression.Loop 本质上是一个 while 循环)。尝试构建一个。
  • 我还发现这个博客系列的前几篇文章很有帮助:blogs.msdn.com/b/mattwar/archive/2008/11/18/linq-links.aspx
  • 谢谢,@Shlomo。在过去一年左右的时间里,我浏览了 The Wayward Weblog 上的帖子。我将很快编写代码来编写和访问 System.Linq.Expressions 命名空间中的所有 73 个表达式家族(我一直在计算和分类)。 :-)
【解决方案2】:

标签值的目的似乎是提供返回值。如果你看我下面的示例代码,“payload”的返回值被传递给“target”标签并成为表达式的返回码。我尝试了 Expression.Return 和 Expression.Break 并得到了相同的结果。 Expression.Continue 没有将值传递给 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()();

这个表达式大致相当于下面的方法。

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

解决第二个问题:您总是“前往”标签目标。 “返回”、“继续”和“中断”是您可能如何“到达”目标的样式。 Return 意味着标签目标位于方法的末尾,并且您将返回值传递给它。 Continue 和 Break 表示标签目标正在参与循环。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-05-05
    • 1970-01-01
    • 1970-01-01
    • 2020-03-12
    • 2014-05-15
    • 2017-01-30
    • 2021-07-10
    相关资源
    最近更新 更多