如何通过堆栈跟踪访问本地? (模仿动态范围)

背景

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

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

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

这个问题

所以我的问题是(无论它是否真的有用;))是否可以通过使用reflection来模拟.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 } 

这是不可能的。 在编译的.NET代码(中间语言)中,变量简单地表示为堆栈上的索引。 例如,加载变量值的ldloc指令只接受unsigned int16值作为参数。 对于在调试模式下编译的应用程序,可能有某种方法可以执行此操作(毕竟,在调试应用程序时,Visual Studio会这样做),但它通常无法正常工作。

在Phalanger(用于.NET的PHP编译器,我部分参与其中),这必须以某种方式解决,因为PHP语言具有eval (它不需要提供动态范围 ,但它需要按名称访问变量) 。 因此,Phalanger检测函数是否包含eval任何使用,如果是,它将所有变量存储在Dictionary ,然后将其传递给eval函数(以便它可以按名称读取变量)。 我担心这是唯一的方法……

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

Reflection允许对在程序集元数据中表示的项目进行运行时操作。

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

因此,你的问题的答案是“不”。

(如何)是否可以可靠地访问调用方法的本地?

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

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

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

如果您想要的是一个进程内 “eval”function,请考虑JScript.NET,这是一种专为此类设计的语言。