Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/315.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/oop/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 非赋值类的调用方法_C#_Oop_Il - Fatal编程技术网

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”的方法是合法的

现在也许你明白了这是怎么回事

当在空引用接收器上进行调用时,是否需要C#与空解引用异常崩溃

。当您使用空接收器调用某个对象时,需要C#崩溃。因此,在生成调用方法的代码时,C#有以下选择:

  • 案例1:生成检查null的IL,然后生成调用
  • 案例2:生成一个callvirt
  • 案例3:生成调用,但不要以空检查开始
案例1简直是哑巴。IL更大,因此占用更多磁盘空间,加载速度较慢,jit速度较慢。当callvirt自动执行空检查时生成此代码是愚蠢的

案例2很聪明。C#编译器生成callvirt,以便自动完成空检查

那么案例3呢?在什么情况下C#可以跳过空检查并生成调用?仅当:

  • 该调用是非虚拟方法调用,并且
  • C#已经知道接收器不是空的
但是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