-->

How to set a breakpoint in a lambda expression?

2019-04-14 22:21发布

问题:

I would like to debug a lambda that is called in an expression tree. Unfortunately, the breakpoint is never hit.

Here's a full Console Program to play with:

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

If I set a breakpoint within the lambda (i.e. at x + 1 using F9) the whole line gets breaked at but not the lambda when it is actually being executed.

Looking at the debug view of the body I see:

.Block(System.Int32 $result) {
    $result = $x + 1;
    .Call System.Console.WriteLine($result)
}

which indicates a copy-semantic: the logic of the lambda has been "inlined" and I suppose the connection to the original lambda is lost. Or would there be any trick to making debugging of the original lambda within Visual Studio possible?

回答1:

An Expression is data, not code. It can be turned into code, by calling Compile(), but until you do that, it's not code. It's data. And the debugger can only set breakpoints in code.

More specifically, the debugger uses the information in the .pdb file generated at compile time, to correlate the compiled code with the original source code. When you use a lambda to define an Expression, you're not generating any compiled code at that time, so there's nothing in the .pdb that would point the debugger to the code in your lambda. You're generating an expression tree, which is a kind of data that can be turned into code later at run-time.

(Not that it is in theory impossible for the debugger to support this, but it would take a lot of extra work, work that hasn't been done, as far as I know.)

Depending on what your ultimate goal is, you might be able to achieve what you want by adding a level of indirection. For example:

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

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

This approach, of course, will hide the actual expression body from the EvalAndWrite() method. If you were using expressions as a way of "decompiling" the original lambda so you could inspect it or otherwise use the individual parts of the body for some reason, then the above wouldn't be useful. But in your example, you don't seem to be doing that. So maybe this will be enough for your needs.