未赋值的调用方法class

Calling method of non-assigned class

我对这两方面有疑问;

第一个;

        Test test = new Test();

        result = test.DoWork(_param);

第二个;

       result = new Test().DoWork(_param);

如果我们不把新建的实例赋值给变量,直接调用方法会怎样?

我发现两种方式在 IL 代码上有些不同。


下面这个是第一个 c# 代码的 IL 输出

 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

而这个是第二个c#代码的IL输出

 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

你能告诉我吗?

如果你不做任何其他事情,那么这两种做法之间没有明显的区别。

唯一真正的区别是,在第一种情况下,您将 Test 实例分配给一个变量,这样您就可以在调试器中访问它 later/inspect。在第二种情况下你不能这样做。

在逻辑上,如果你以后不对 test 做任何事情,除了第二种情况下的性能提升非常小(我想不出这么小)之外,没有什么区别任何可能重要的实际场景)。

这里的问题有点难找,但我想你问的是:

why does assigning the newly created reference to a variable cause the compiler to generate a callvirt, but calling the method directly generates a call?

你很细心,能注意到这种细微的差别。

在回答您的问题之前,让我们先回答一些其他问题。

Should I trust that the compiler generates good code?

一般来说是的。偶尔会出现代码生成错误,但这不是其中之一。

Is it legal to call a non-virtual method with callvirt?

是的。

Is it legal to call a virtual method with call?

是的,如果您尝试调用基本 class 方法而不是派生 class 中的覆盖。但通常不会发生这种情况。

Is the method being called in this example virtual or not?

不是虚拟的。

Since the method is not virtual, it could be called with callvirt or call. Why does the compiler sometimes generate callvirt and sometimes generate call, when it could generate callvirt both times or call both times, consistently?

现在我们进入您问题中有趣的部分。

call 和callvirt 有两个区别。

  • 调用不进行虚拟调度; callvirt 在调用之前在虚函数 dispatch table 中查找正确的方法。因此,callvirt 大约慢了一纳秒。

  • callvirt 总是检查接收者是否为空,不管调用的方法是否是虚拟的。 call 不检查接收者是否为空。通过 call.

  • 调用带有 null "this" 的方法是合法的

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

Is C# required to crash with a null dereference exception whenever a call is made on a null reference receiver?

。 C# 要求 在您使用空接收器调用某些内容时崩溃。因此C#在生成调用方法的代码时有如下选择:

  • 案例 1:生成检查 null 的 IL,然后生成调用。
  • 案例二:生成callvirt。
  • 情况 3:生成调用,但不以空检查开始。

案例 1 很愚蠢。 IL 更大,因此占用更多磁盘空间,加载更慢,jit 更慢。当 callvirt 自动执行空检查时生成此代码将是愚蠢的。

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

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

  • 调用是non-virtual方法调用,
  • C#已经知道接收者不为空

但是 C# 知道在 new Foo().Bar() 中接收者不能为空,因为如果是,那么构造会抛出异常,我们永远无法调用!

编译器不够聪明,无法意识到变量只被赋予了non-null值。所以它生成一个callvirt是安全的。

编译器可以写得那么聪明。编译器已经必须跟踪变量的赋值状态以进行明确赋值检查。它还可以跟踪 "was assigned something that might be null" 状态,然后在这两种情况下都会生成调用。但是编译器(还)没有那么聪明。