如何在 lambda 表达式中设置断点?
How to set a breakpoint in a lambda expression?
我想调试在表达式树中调用的 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 中设置一个断点(即在 x + 1
使用 F9)整行在实际执行时被中断但不是 lambda .
查看 body
的调试视图,我看到:
.Block(System.Int32 $result) {
$result = $x + 1;
.Call System.Console.WriteLine($result)
}
这表示复制语义:lambda 的逻辑已经 "inlined" 并且我想与原始 lambda 的连接丢失了。或者是否有任何技巧可以在 Visual Studio 内调试原始 lambda?
Expression
是数据,不是代码。它可以通过调用 Compile()
变成 代码,但在您这样做之前,它不是代码。是数据。而且调试器只能在代码中设置断点。
更具体地说,调试器使用编译时生成的 .pdb 文件中的信息,将编译后的代码与原始源代码相关联。当您使用 lambda 定义 Expression
时,您当时不会生成任何编译代码,因此 .pdb 中没有任何内容可以将调试器指向您的 lambda 中的代码。您正在生成一个表达式树,这是一种数据,稍后可以在 run-time.
处转换为代码
(并不是说调试器在理论上不可能支持这个,但是这需要很多额外的工作,据我所知还没有完成的工作。)
根据您的最终目标,您可以通过添加一个间接级别来实现您想要的。例如:
private static void Main()
{
Func<int, int> e = x => x + 1; // set breakpoint here
var evalAndWrite = EvalAndWrite(x => e(x));
evalAndWrite(1);
Console.ReadLine();
}
当然,这种方法会从 EvalAndWrite()
方法中隐藏实际表达式 body。如果您使用表达式作为 "decompiling" 原始 lambda 的一种方式,以便您可以检查它或出于某种原因以其他方式使用 body 的各个部分,那么上面的内容将没有用。但在你的例子中,你似乎并没有这样做。所以也许这足以满足您的需求。
我想调试在表达式树中调用的 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 中设置一个断点(即在 x + 1
使用 F9)整行在实际执行时被中断但不是 lambda .
查看 body
的调试视图,我看到:
.Block(System.Int32 $result) {
$result = $x + 1;
.Call System.Console.WriteLine($result)
}
这表示复制语义:lambda 的逻辑已经 "inlined" 并且我想与原始 lambda 的连接丢失了。或者是否有任何技巧可以在 Visual Studio 内调试原始 lambda?
Expression
是数据,不是代码。它可以通过调用 Compile()
变成 代码,但在您这样做之前,它不是代码。是数据。而且调试器只能在代码中设置断点。
更具体地说,调试器使用编译时生成的 .pdb 文件中的信息,将编译后的代码与原始源代码相关联。当您使用 lambda 定义 Expression
时,您当时不会生成任何编译代码,因此 .pdb 中没有任何内容可以将调试器指向您的 lambda 中的代码。您正在生成一个表达式树,这是一种数据,稍后可以在 run-time.
(并不是说调试器在理论上不可能支持这个,但是这需要很多额外的工作,据我所知还没有完成的工作。)
根据您的最终目标,您可以通过添加一个间接级别来实现您想要的。例如:
private static void Main()
{
Func<int, int> e = x => x + 1; // set breakpoint here
var evalAndWrite = EvalAndWrite(x => e(x));
evalAndWrite(1);
Console.ReadLine();
}
当然,这种方法会从 EvalAndWrite()
方法中隐藏实际表达式 body。如果您使用表达式作为 "decompiling" 原始 lambda 的一种方式,以便您可以检查它或出于某种原因以其他方式使用 body 的各个部分,那么上面的内容将没有用。但在你的例子中,你似乎并没有这样做。所以也许这足以满足您的需求。