有没有办法告诉调用c#函数的方法的参数?
我正在为我的c#应用程序开发一个免提日志机制。
这是我想要的样子:
函数a(arg1, arg2, arg 3.....)
调用函数b(arg4,arg5,arg6....)
,后者又调用log()
,它能够检测b(arg4,arg5,arg6....)
(这可以完成)通过Environment.StackTrace
)以及调用stacktrace中每个函数(例如a
和b
)的值。
我希望它在调试和发布模式下工作(或者至少在调试模式下)。
这可以在.net中做吗?
绝对不可能:
在调用b
, arg1
使用的堆栈中的空间(IL堆栈,因此可能它从未放入堆栈,但已在调用中注册)并不保证仍由arg1
。
通过扩展,如果arg1
是引用类型,则如果在调用b
之后未使用它,则不保证它所引用的对象不被垃圾收集。
编辑 :
更详细一点,因为你的评论暗示你不是喜欢这个并且仍然认为它应该是可能的。
抖动使用的调用约定未在任何相关标准的规范中指定,这使得实现者可以自由地进行改进。 它们确实在32位和64位版本以及不同版本之间存在差异。
但是,来自MS人员的文章表明,所使用的约定类似于__fastcall约定。 在你的a调用中, arg1
将被放入ECX寄存器*,并且arg2
进入EDX寄存器(我通过假设32位x86进行简化,其中amd64甚至更多的参数被注册)代码运行的核心。 arg3
将被推入堆栈并确实存在于内存中。
注意,此时,没有存在arg1
和arg2
存储单元,它们只存在于CPU寄存器中。
在执行方法本身的过程中,根据需要使用寄存器和存储器。 并且b
被称为。
现在,如果a
需要arg1
或arg2
它必须在调用b
之前推送它。 但如果没有,那么它就不会 – 甚至可能会重新订购以减少这种需求。 相反,这些寄存器可能已经被用于其他东西 – 抖动并不是愚蠢的,所以如果它需要一个寄存器或堆栈上的一个插槽而且有一个未使用该方法的其余部分,它就会发生重用那个空间。 (就此而言,在上面这个级别,C#编译器将重用IL生成的虚拟堆栈中的槽使用)。
因此,当调用b
时, arg4
被置于寄存器ECX中, arg5
被置于EDX中, arg6
被置于堆栈中。 此时, arg1
和arg2
不存在,你不能再发现它们是什么,而不是你可以在它被回收并变成卫生纸后读书。
(有趣的是,一个方法在同一个位置调用另一个具有相同参数的方法是很常见的,在这种情况下,ECX和EDX可以保持不变)。
然后, b
返回,将其返回值放在EAX寄存器中,或者将EDX:EAX对或内存中的EAX指向它,具体取决于大小,在将其返回到该寄存器之前再做一些工作,依此类推。
现在,假设没有做任何优化。 事实上, b
可能根本没有被调用,而是它的代码被内联。 在这种情况下,寄存器中或堆栈中的值 – 以及后一种情况下它们在堆栈中的位置 – 是否与b
的签名不再有任何关系,以及与相关值在何处相关a
执行,并且在b
的另一个“呼叫”的情况下会有所不同,或者甚至在从a
的另一个“呼叫” b
的情况下,因为包括其对b
呼叫的整个呼叫都可以被内联在一种情况下,不在另一种情况下内联,在另一种情况下以不同方式内联。 例如,如果arg4
直接来自另一个调用返回的值,则此时它可能位于EAX寄存器中,而arg5
位于ECX中,因为它与arg1
相同,而arg6
位于中间的某个位置。堆栈空间正在使用。
另一种可能性是对b
的调用是一个被淘汰的尾调用:因为对b
的调用将使其返回值立即由(或其他一些可能性)返回,然后不是推到堆栈, a
使用的值被替换为原位,并且返回地址发生了变化,以便b
的返回跳转回调用a
的方法,跳过一些工作(并减少内存使用,达到某些function样式接近的程度)会溢出堆栈而不是工作,确实工作得很好)。 在这种情况下,在调用b
期间, b
的参数可能已完全消失,即使是那些已经在堆栈中的参数。
最后的案例是否应该被视为优化,这是值得商榷的; 有些语言在很大程度上依赖于它的完成,因为它们具有良好的性能,如果它们甚至可以工作而不会产生可怕的性能(而不是溢出堆栈)。
可以有各种其他优化方式。 应该有各种各样的其他优化 – 如果.NET团队或Mono团队做了一些使我的代码更快或使用更少的内存但行为相同的东西,而不需要我的东西,我一个人不会抱怨!
而且假设首先编写C#的人从未改变过参数的值,这当然不会是真的。 考虑以下代码:
IEnumerable RepeatedlyInvoke(Func factory, int count) { if(count < 0) throw new ArgumentOutOfRangeException(); while(count-- != 0) yield return factory(); }
即使C#编译器和抖动是以如此浪费的方式设计的,你可以保证参数不会以上述方式改变,你怎么能知道在factory
调用中已经有多少count
? 即使在第一次通话时它也是不同的,并且它不像以上那样是奇怪的代码。
所以,总结一下:
- 抖动:通常会注册参数。 你可以期望x86在寄存器和amd64中放入2个指针,引用或整数参数,将4个指针,引用或整数参数和4个浮点参数放入寄存器。 他们没有地方可以阅读。
- 抖动:堆栈上的参数经常被覆盖。
- 抖动:可能根本没有真正的呼叫,所以没有地方可以查找参数,因为它们可能在任何地方。
- 抖动:“呼叫”可能重新使用与最后一帧相同的帧。
- 编译器:IL可以为本地人重复使用插槽。
- 人:程序员可能会更改参数值。
从这一切来看,究竟怎样才能知道arg1
是什么?
现在,添加垃圾收集的存在。 想象一下,如果我们能够神奇地知道arg1
究竟是什么,尽管如此。 如果它是对堆上对象的引用,它可能仍然对我们没有好处,因为如果所有上述意味着堆栈上没有更多的引用活动 - 并且应该很清楚,这肯定会发生 - 并且GC启动,然后可以收集对象。 因此,我们可以神奇地掌握的是对不再存在的东西的引用 - 实际上很可能是堆中的某个区域现在被用于其他东西,bang是整个框架的整个类型安全!
与获得IL的reflection相比,它没有丝毫的可比性,因为:
- IL是静态的,而不仅仅是给定时间点的状态。 同样地,我们可以更容易地从图书馆获得我们最喜欢的书籍的副本,而不是在我们第一次阅读它们时能够得到我们的反应。
- 无论如何,IL并未反映内联等的影响。 如果每次实际使用时调用内联,然后我们使用reflection来获取该方法的
MethodBody
,那么它通常内联的事实是无关紧要的。
关于性能分析,AOP和拦截的其他答案中的建议尽可能接近。
*实际上, this
是实例成员的真正第一个参数。 让我们假装一切都是静态的,所以我们不必一直指出这一点。
在.net中是不可能的。 在运行时,JITter可能决定使用CPU寄存器而不是堆栈来存储方法参数,甚至可以重写堆栈中的初始(传递)值。 因此,.net允许在源代码中的任何位置记录参数,这将是非常高性能的。
据我所知,一般来说,唯一的方法就是使用.net CLR分析API。 (例如,Typemock框架能够执行此类操作并使用CLR分析API)
如果您只需要拦截虚拟函数/属性(包括接口方法/属性)调用,则可以使用任何拦截框架(例如Unity或Castle)。
有一些关于.net分析API的信息:
MSDN杂志
MSDN博客
Brian Long的博客
这在C#中是不可能的,您应该使用AOP方法并在调用每个方法时执行方法参数记录。 这样,您可以集中日志代码,使其可重用,然后您只需要标记哪些方法需要参数日志记录。
我相信使用像PostSharp这样的AOP框架可以轻松实现这一点。
如果没有类型模拟或某些ICorDebug魔法,可能不会发生。 甚至StackFrame类也只列出允许您获取有关源的信息的成员,而不是参数。
但是,您所使用的function作为IntelliTrace与方法日志记录一起存在。 您可以过滤审核所需的内容。