有什么方法可以告诉在C#中调用函数的方法的参数吗? [英] Is there any way to tell the arguments of the method calling a function in c#?

查看:41
本文介绍了有什么方法可以告诉在C#中调用函数的方法的参数吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在为我的C#应用​​程序开发一种免提日志机制.

I'm working on a hands-off log mechanism for my c# application.

这就是我想要的样子:

函数 a(arg1,arg2,arg 3 .....)调用函数 b(arg4,arg5,arg6 ....)调用 log(),该日志将能够检测堆栈跟踪(可通过 Environment.StackTrace 完成)和每个函数使用的值(例如 a b )在堆栈跟踪中被调用.

function a(arg1, arg2, arg 3.....) calls function b(arg4,arg5,arg6....), which in turn calls log() which is than able to detect the stacktrace (this can be done via Environment.StackTrace) and the values with which each function (e.g. a and b) in the stacktrace is called.

我希望它在调试和发布模式下工作(或者至少在调试模式下工作).

I want it to work in debug and release mode (or, at least, in debug mode).

在.net中可以这样做吗?

Is this possible to do in .net?

推荐答案

可能无法实现:

在调用 b 时,a的 arg1 (IL堆栈使用的堆栈中的空间,因此可能甚至从未放入堆栈中,但已经在通话中进行了注册)并不能保证 arg1 仍在使用.

By the time b is called, the space in the stack used by a's arg1 (the IL stack, so possibly it was never even put in a stack, but had been enregistered on the call) is not guaranteed to still be used by arg1.

通过扩展,如果 arg1 是引用类型,则不能保证所引用的对象没有被垃圾回收,如果在调用 b之后未使用该对象.

By extension, if arg1 is a reference-type, the object it referred to is not guaranteed to not have been garbage collected, if it isn't used after the call to b.

修改:

更多细节,因为您的评论表明您并没有在意这一点,但仍然认为应该可行.

A bit more detail, since your comment suggests you're not grokking this and still think it should be possible.

规范中没有为任何相关标准指定抖动所使用的调用约定,这使实现者可以自由地进行改进.实际上,它们在32位和64位版本以及不同的发行版之间确实有所不同.

The calling conventions used by the jitter are not specified in the specs for any of the relevant standards, which gives implementers freedom to make improvements. They do indeed differ between 32-bit and 64-bit versions, and different releases.

但是,来自MS人士的文章建议所使用的约定类似于 __fastcall 约定.在您调用 a 时,会将 arg1 放入ECX寄存器*,并将 arg2 放入EDX寄存器(我通过简化来简化运行代码的核心的32位x86(带有amd64甚至注册了更多参数). arg3 将被压入堆栈,并且确实存在于内存中.

However, articles from MS people suggest that the convention used is akin to the __fastcall convention. In your call to a, arg1 would be put into the ECX register*, and arg2 into the EDX register (I'm simplifying by assuming 32-bit x86, with amd64 even more arguments are enregistered) of the core the code is running on. arg3 would be pushed on the stack and would indeed exist in memory.

请注意,此时, arg1 arg2 不存在内存位置,它们仅在CPU寄存器中.

Note that at this point, there is no memory location in which arg1 and arg2 exist, they're only in a CPU register.

在执行方法本身的过程中,必要时使用寄存器和存储器.然后 b 被调用.

In the course of executing the method itself, the registers and memory are used as necessary. And the b is called.

现在,如果 a 需要 arg1 arg2 ,则必须在调用 b <之前将其推送/code>.但是,如果没有,那么就不会-甚至可能会重新排序以减少这种需求.相反,到目前为止,这些寄存器可能已经被用于其他用途-抖动并不愚蠢,因此,如果它需要一个寄存器或堆栈上的一个插槽,而其余的方法都没有使用,那就是重用该空间.(为此,在此级别之上,C#编译器将重用产生的IL使用的虚拟堆栈中的插槽.)

Now, if a is going to need arg1 or arg2 it'll have to push that before it calls b. But if it doesn't, then it won't - and things might even be re-ordered to reduce this need. Conversely, those registers may have already been used for something else already by this point - the jitter isn't stupid, so if it needs a register or a slot on the stack and there's one going unused for the rest of the method, it's going to reuse that space. (For that matter, at the level above this, the C# compiler will reuse slots in the virtual stack that the IL produced uses).

因此,当调用 b 时,将 arg4 放置在寄存器ECX中,将 arg5 放入EDX和 arg6 中推入堆栈.此时, arg1 arg2 不存在,您再也无法找出它们的含义,就像将书回收再利用后变成厕所一样,可以看书.纸.

So, when b is called, arg4 is placed in register ECX, arg5 into EDX and arg6 pushed on the stack. At this point, arg1 and arg2 don't exist and you can no longer find out what they were than you can read a book after it has been recycled and turned into toilet paper.

(有趣的是,一种方法在相同位置调用另一个具有相同参数的方法是很常见的,在这种情况下,可以单独保留ECX和EDX).

(Interesting note is that it's very common for a method to call another with the same arguments in the same position, in which case ECX and EDX can be just left alone).

然后, b 返回,并将其返回值放入EAX寄存器或EDX:EAX对或EAX指向它的内存中,具体取决于大小 a 在将返回值放入该寄存器之前还要做更多工作,等等.

Then, b returns, putting its return value in the EAX register, or EDX:EAX pair or in memory with EAX pointing to it, depending on size, a does some more work before putting its return in that register, and so on.

现在,这是假设尚未进行任何优化.实际上,根本没有调用 b ,而是内联了它的代码.在这种情况下,值是在寄存器中还是在堆栈中-在后者的情况下,它们在堆栈中的位置都不再与 b 的签名有关,而与之无关其中相关值是在 a 执行期间,并且在另一个调用"到 b 的情况下,甚至在另一个调用"的情况下,也会有所不同从 a b ,因为整个 a 调用(包括对 b 的调用)都可以内联一个案例,不在另一个案例中进行内联,而在另一个案例中进行不同的内联.例如,如果 arg4 直接来自另一个调用返回的值,则此时它可能在EAX寄存器中,而 arg5 在ECX中是相同的因为 arg1 arg6 位于 a 使用的堆栈空间中间的某个位置.

Now, this is assuming there haven't been any optimisations done. It's possible that in fact, b wasn't called at all, but rather that its code was inlined. In this case whether the values where in registers or on the stack - and in the latter case, where they were on the stack, no longer has anything to do with b's signature and everything to do with where the relevant values are during a's execution, and it would be different in the case of another "call" to b, or even in the case of another "call" to b from a, since the entire call of a including its call to b could have been inlined in one case, not inlined in another, and inlined differently in yet another. If for example, arg4 came straight from a value returned by another call, it could be in the EAX register at this point, while arg5 was in ECX as it was the same as arg1 and arg6 was somewhere half-way in the middle of the stack-space being used by a.

另一种可能性是,对 b 的调用被消除了尾部调用:因为对 b 的调用也将立即由其返回值, a (或其他一些可能性),然后而不是将其压入堆栈,而是将 a 使用的值就地替换,并更改返回地址,以便从 b 返回的方法返回到称为 a 的方法,跳过一些工作(并在某种程度上减少了使用函数的方式来使堆栈溢出,从而减少了内存使用)工作,确实工作得很好).在这种情况下,在调用 b 的过程中, a 的参数可能会完全消失,即使那些在堆栈中的参数也是如此.

Another possibility is that the call to b was a tail-call that was eliminated: Because the call to b was going to have its return value immediately returned too by a (or some other possibilities), then rather than pushing to the stack, the values being used by a are replaced in-place, and the return address changed so that the return from b jumps back to the method that called a, skipping some of the work (and reducing memory use to the extent that some functional style approaches that would overflow the stack instead work and indeed work well). In this case, during the call to b, the parameters to a are likely completely gone, even those that had been on the stack.

最后一种情况是否应该被认为是所有优化都值得商;.有些语言在很大程度上取决于它的完成情况,因为它们提供了良好的性能,即使它们完全可以工作,也不会给它们带来可怕的性能(而不是使堆栈溢出).

It's highly debatable whether this last case should even be considered an optimisation at all; some languages heavily depend upon it being done as with it they give good performance and without they give horrible performance if they even work at all (instead of overflowing the stack).

可以有各种其他优化方式.应该进行各种其他优化-如果.NET团队或Mono团队所做的事情可以使我的代码更快或使用更少的内存,但是在没有其他要求的情况下,我的行为却相同,我可以一个人不会抱怨!

There can be all manner of other optimisations. There should be all manner of other optimisations - if the .NET team or the Mono team do something that makes my code faster or use less memory but otherwise behave the same, without my having to something, I for one won't be complaining!

这是假设首先编写C#的人从未更改过参数的值,这肯定不是正确的.考虑以下代码:

And that's assuming that the person writing the C# in the first place never changed the value of a parameter, which certainly isn't going to be true. Consider this code:

IEnumerable<T> RepeatedlyInvoke(Func<T> factory, int count)
{
  if(count < 0)
    throw new ArgumentOutOfRangeException();
  while(count-- != 0)
    yield return factory();
}

即使以一种浪费的方式设计了C#编译器和抖动,以至于您可以保证不会以上述方式更改参数,您怎么知道什么 count 已经存在从 factory 的调用中?即使在第一次调用时,它也有所不同,并且也不像上面的代码是奇怪的代码.

Even if the C# compiler and the jitter had been designed in such a wasteful way that you could guarantee parameters weren't changed in the ways described above, how could you know what count had already been from within the invocation of factory? Even on the first call it's different, and it's not like the above is strange code.

因此,总而言之:

  1. 抖动:通常会注册参数.您可以期望x86将2个指针,引用或整数参数放入寄存器中,而amd64将4个指针,引用或整数参数以及4个浮点参数放入寄存器中.他们没有位置可以读取它们.
  2. 抖动:堆栈上的参数通常会被覆盖.
  3. 抖动:可能根本没有一个真正的电话,因此没有地方可以查找参数,因为它们可能在任何地方.
  4. 抖动:呼叫"可能正在重新使用与上一个相同的帧.
  5. 编译器:IL可能会为本地人重新使用插槽.
  6. 人:程序员可以更改参数值.

从所有方面来看,究竟怎么可能知道 arg1 是什么?

From all of that, how on earth is it going to be possible to know what arg1 was?

现在,添加垃圾回收的存在.试想一下,尽管如此,我们是否能神奇地知道什么是 arg1 .如果这是对堆上对象的引用,那么它可能仍然对我们没有好处,因为如果以上所有内容都意味着堆栈上没有更多的引用处于活动状态,那么很明显这确实会发生-并且GC启动,那么该对象可能已被收集.因此,我们可以神奇地掌握的是对一个已不存在的东西的引用-实际上很可能是对现在用于其他用途的堆中某个区域的支持,爆炸使整个框架的整个类型安全!

Now, add in the existence of garbage collection. Imagine if we could magically know what arg1 was anyway, despite all of this. If it was a reference to an object on the heap, it might still do us no good, because if all of the above meant that there were no more references active on the stack - and it should be clear that this quite definitely does happen - and the GC kicks in, then the object could have been collected. So all we can magically get hold of is a reference to something that no longer exists - indeed quite possibly to an area in the heap now being used for something else, bang goes the entire type safety of the entire framework!

这与反射获得IL丝毫没有可比性,因为:

It's not in the slightest bit comparable to reflection obtaining the IL, because:

  1. IL是静态的,而不只是给定时间点的状态.同样,与第一次阅读它们时得到的反馈相比,我们可以更轻松地从图书馆中获得我们喜欢的书的副本.
  2. 无论如何,IL都不反映内联等的影响.如果每次实际使用时都内联一个调用,然后我们使用反射来获得该方法的 MethodBody ,则通常内联的调用是无关紧要的.
  1. The IL is static, rather than just a state at a given point in time. Likewise, we can get a copy of our favourite books from a library a lot more easily than we can get back our reaction the first time we read them.
  2. The IL doesn't reflect the impact of inlining etc. anyway. If a call was inlined every time it was actually used, and then we used reflection to get a MethodBody of that method, the fact that its normally inlined is irrelevant.

其他答案中关于概要分析,AOP和拦截的建议与您将要得到的差不多.

The suggestions in other answers about profiling, AOP, and interception are as close as you're going to get.

*实际上, this 是实例成员的真正第一个参数.让我们假装一切都是静态的,所以我们不必一直指出这一点.

*Actually, this is the real first parameter to instance members. Lets pretend everything is static so we don't have to keep pointing this out.

这篇关于有什么方法可以告诉在C#中调用函数的方法的参数吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆