7

背景

即使可以在运行时编译 C# 代码,也不可能在当前范围内包含并运行生成的代码。相反,所有变量都必须作为显式参数传递。

与 Python 等动态编程语言相比,我们永远无法真正复制eval(如本例所示)的完整行为。

x = 42
print(eval("x + 1")) # Prints 43

问题

所以我的问题是(不管它是否真的有用;))是否可以通过使用反射来模拟.NET 中的动态范围

由于 .NET 为我们提供了Diagnostics.StackTrace允许我们检查调用方法的类,所以这个问题归结为以下几点:(如何)可以可靠地访问调用方法的本地变量吗?

堆栈跟踪是否为我们提供了足够的信息来计算内存偏移量,或者在托管代码中是否禁止此类事情?

这样的代码有可能吗?

void Foo() {
   int x = 42;
   Console.WriteLine(Bar());
}

int Bar() {
   return (int)(DynamicScope.Resolve("x")); // Will access Foo's x = 42
}
4

2 回答 2

8

所以我的问题是是否可以通过使用反射来模拟.NET 中的动态范围。

反射支持对在程序集的元数据中表达的项目进行运行时操作。

局部变量不是在程序集的元数据中表达的项目。

因此,您的问题的答案是“否”。

(如何)可以可靠地访问调用方法的本地变量吗?

访问调用方法的局部变量的设备称为“调试器”。所以你的问题的答案是“给自己写一个调试器”。

请注意,调试器无法可靠地访问具有代码优化的世界中的本地人。局部变量可以被优化掉,抖动可以为两个不同的局部变量生成使用相同寄存器的代码,堆栈帧可以在尾调用期间重用,等等。当然,您最好有一个 PDB 文件来告诉调试器与每个堆栈帧位置关联的名称是什么。

您的方法必须做的是将您的自定义调试器作为新进程启动,然后等待调试器向其发送消息。然后调试器会挂起原始进程的线程,询问堆栈帧,然后恢复进程的线程。然后它将向被调试者发送包含您发现的信息的消息。由于被调试者处于等待消息的等待状态,因此它将恢复其业务。

如果您想要的是进程内“评估”功能,请考虑 JScript.NET,这是一种专为此类事情设计的语言。

于 2010-02-14T17:28:20.133 回答
5

这是不可能的。在已编译的 .NET 代码(中间语言)中,变量仅表示为堆栈上的索引。例如,加载变量值ldloc指令unsigned int16仅将值作为参数。对于在调试模式下编译的应用程序,可能有一些方法可以做到这一点(毕竟,在调试应用程序时,Visual Studio 会这样做),但它不能正常工作。

在 Phalanger(.NET 的 PHP 编译器,我参与其中)中,必须以某种方式解决这个问题,因为 PHP 语言有eval(它不需要提供动态作用域,但它需要按名称访问变量)。因此,Phalanger 检测一个函数是否包含对的任何使用,eval如果有,它将所有变量存储在 中Dictionary<string, object>,然后将其传递给eval函数(以便它可以通过变量名读取变量)。恐怕这是唯一的方法...

于 2010-02-14T16:12:44.867 回答