C# 非赋值类的调用方法
我对这两方面有怀疑, 第一个C# 非赋值类的调用方法,c#,oop,il,C#,Oop,Il,我对这两方面有怀疑, 第一个 Test test = new Test(); result = test.DoWork(_param); 第二个 result = new Test().DoWork(_param); 如果我们不将新创建的实例分配给变量并直接调用该方法,会发生什么 我在IL代码上看到了两种方法之间的一些差异 下面这个是第一个c代码的IL输出 这是第二个c代码的IL输出 您能告诉我吗?如果您不做任何其他事情,这两种方法之间没
Test test = new Test();
result = test.DoWork(_param);
第二个
result = new Test().DoWork(_param);
如果我们不将新创建的实例分配给变量并直接调用该方法,会发生什么
我在IL代码上看到了两种方法之间的一些差异
下面这个是第一个c代码的IL输出
这是第二个c代码的IL输出
您能告诉我吗?如果您不做任何其他事情,这两种方法之间没有明显的区别 唯一真正的区别是,在第一种情况下,您将
Test
实例分配给一个变量,以便以后可以在调试器中访问/检查它。在第二种情况下,您不能这样做
就逻辑而言,如果您以后不使用
test
做任何事情,那么除了第二种情况下的一个非常微小的性能改进之外,没有什么区别(如此微小,我想不出任何实际情况下它可能起作用)。这里的问题有点难找到,但我认为您要问的是:
为什么将新创建的引用分配给变量会导致编译器生成callvirt,而直接调用该方法会生成调用
你很敏锐地注意到了这种细微的差别
在讨论你的问题之前,让我们回答一些其他问题
我应该相信编译器会生成好的代码吗
一般来说是的。偶尔会出现代码生成错误,但这不是其中之一
使用callvirt调用非虚拟方法合法吗
对
用call调用虚拟方法合法吗
是的,如果您试图调用基类方法,而不是派生类中的重写。但这种情况通常不会发生
本例中调用的方法是虚拟的还是非虚拟的
它不是虚拟的
由于该方法不是虚拟的,因此可以使用callvirt或call调用它。为什么编译器有时会生成callvirt,有时会生成call,而它可以同时生成callvirt或同时调用这两次,这是一致的
现在我们进入你问题的有趣部分
call和callvirt之间有两个区别
- 呼叫不进行虚拟调度;callvirt在调用虚拟函数分派表之前会在其中查找正确的方法。因此callvirt大约慢一纳秒
- callvirt总是检查接收方是否为null,而不管调用的方法是否为virtual。调用不检查接收方是否为空。通过调用调用带有空“this”的方法是合法的
- 案例1:生成检查null的IL,然后生成调用李>
- 案例2:生成一个callvirt
- 案例3:生成调用,但不要以空检查开始
- 该调用是非虚拟方法调用,并且
- C#已经知道接收器不是空的
new Foo().Bar()
中,接收方不能为空,因为如果为空,那么构造将抛出异常,我们将永远无法进行调用
编译器不够聪明,无法意识到变量只被分配了非空值。因此它生成了一个安全的callvirt
编译器可以写得那么聪明。编译器已经必须跟踪变量的赋值状态,以进行明确的赋值检查。它还可以跟踪“已分配可能为null的内容”状态,然后在这两种情况下都会生成调用。但是编译器还没有那么聪明。在一种情况下,你可以在调用后访问对象,而在另一种情况下,你不能。好吧,第二种情况下,不会在堆栈上分配。它没有在堆上创建对测试对象的引用,而是简单地执行DoWork()@ChrisWohlert:我向您保证,无论如何都会在计算堆栈上创建引用。阅读IL;newobj必须把生成的引用放在某个地方,它把它放在哪里?但是在IL指令上有一些区别,一条指令调用
callvirt
,另一条指令调用call
,这会起作用吗?是的,这肯定是我要问的。我通过创建一个基类将DoWork
方法转换为virtual,这次它调用callvirt
,而不是call
。我认为这是编译器中更聪明的部分。
IL_0000: ldstr "job "
IL_0005: stloc.0
IL_0006: newobj instance void Works.Test::.ctor()
IL_000b: stloc.1
IL_000c: ldloc.1
IL_000d: ldloc.0
IL_000e: callvirt instance string Works.Test::DoWork(string)
IL_0013: pop
IL_0014: ret
IL_0000: ldstr "job "
IL_0005: stloc.0
IL_0006: newobj instance void Works.Test::.ctor()
IL_000b: ldloc.0
IL_000c: call instance string Works.Test::DoWork(string)
IL_0011: pop
IL_0012: ret