如何在lambda表达式中设置断点?

10

我希望能够调试一个被表达式树调用的lambda函数。不幸的是,断点永远没有被触发。

以下是可以使用的完整控制台程序:

private static void Main()
{
    var evalAndWrite = EvalAndWrite(x => x + 1 /* a breakpoint here is never hit */);
    evalAndWrite(1);
    Console.ReadLine();
}

private static Action<int> EvalAndWrite(Expression<Func<int, int>> expr)
{
    var result = Expression.Variable(typeof(int), "result");
    var assign = Expression.Assign(result, expr.Body);
    var writeLine = Expression.Call(typeof(Console), nameof(Console.WriteLine), null, result);
    var body = Expression.Block(new[] {result}, assign, writeLine);
    return Expression.Lambda<Action<int>>(body, expr.Parameters[0]).Compile();
}

如果我在lambda内部(即在使用F9的x + 1处)设置断点,则整行会被断点中断,但实际执行lambda时不会中断lambda。查看body的调试视图,我看到:
.Block(System.Int32 $result) {
    $result = $x + 1;
    .Call System.Console.WriteLine($result)
}

这段话表明代码使用了复制语义:lambda函数的逻辑已经“内联”,我认为与原始lambda之间的连接已经丢失了。或者是否有任何诀窍可以使在Visual Studio中调试原始lambda成为可能?

由于您的新 Lambda 函数没有直接调用原始函数,我不知道您如何在一个函数上设置断点,而该断点将被传递到另一个函数。 - juharr
1个回答

17

表达式是数据,而不是代码。通过调用Compile(),它可以被转换为代码,但在此之前,它不是代码,而是数据。调试器只能在代码中设置断点。

更具体地说,调试器使用在编译时生成的.pdb文件中的信息来将编译后的代码与原始源代码进行关联。当您使用lambda定义一个表达式时,此时并没有生成任何已编译的代码,因此.pdb中没有任何内容会指向您lambda中的代码。您正在生成一个表达式树,这是一种数据类型,在运行时稍后可以将其转换为代码。

(理论上,调试器支持这一点并不困难,但需要进行大量额外的工作,据我所知还没有完成这一工作)

根据您的最终目标,您可能可以通过添加间接层来实现您想要的结果。例如:

private static void Main()
{
    Func<int, int> e = x => x + 1; // set breakpoint here

    var evalAndWrite = EvalAndWrite(x => e(x));
    evalAndWrite(1);
    Console.ReadLine();
}

当然,这种方法将会隐藏实际的表达式体(body)不让EvalAndWrite()方法看到。如果你正在使用表达式来"反编译"(decompile)原始的 lambda 表达式以便检查它或以某种方式使用表达式体的各个部分,那么上述方法就没有用了。但在您的示例中,似乎您并没有这样做。所以也许这对您的需求已经足够了。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接