调用非指定类的方法

Emr*_*tar 5 c# oop il

我怀疑这两个方面;

第一;

        Test test = new Test();

        result = test.DoWork(_param);
Run Code Online (Sandbox Code Playgroud)

第二个;

       result = new Test().DoWork(_param);
Run Code Online (Sandbox Code Playgroud)

如果我们不将新创建的实例分配给变量并直接调用该方法会发生什么?

我看到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
Run Code Online (Sandbox Code Playgroud)

这个是第二个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
Run Code Online (Sandbox Code Playgroud)

你能告诉我吗?

Eri*_*ert 21

这个问题有点难以找到,但我想你要问的是:

为什么将新创建的引用分配给变量会导致编译器生成一个callvirt,但调用该方法会直接生成一个调用?

你非常敏锐地注意到这种微妙的差异.

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

我应该相信编译器会生成良好的代码吗?

一般是的.偶尔有代码生成错误,但这不是其中之一.

使用callvirt调用非虚方法是否合法?

是.

通过调用调用虚方法是否合法?

是的,如果您尝试调用基类方法而不是派生类中的覆盖.但通常这不会发生.

该示例中的方法是否为虚拟调用?

这不是虚拟的.

由于该方法不是虚拟的,因此可以使用callvirt或call调用.为什么编译器有时会生成callvirt并且有时会生成call,当它可以生成callvirt时,或者同时调用两个时间?

现在我们来看你问题的有趣部分.

call和callvirt有两个不同之处.

  • 呼叫不做虚拟调度; callvirt在调用它之前在虚函数调度表中查找正确的方法.因此,callvirt的速度要慢一个纳秒.

  • callvirt 总是检查接收者是否为空,无论被调用的方法是否为虚拟.call不检查接收器是否为空.通过调用调用带有"this"的方法是合法的.

现在也许你会看到它的发展方向.

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

是的.当您使用null接收器调用某些内容时,C#必须崩溃.因此,在生成调用方法的代码时,C#有以下选择:

  • 情况1:生成检查null的IL,然后生成一个调用.
  • 案例2:生成一个callvirt.
  • 情况3:生成一个调用,但不要以空检查开始.

案例1只是愚蠢.IL更大,因此在磁盘上占用更多空间,加载速度更慢,jit更慢.当callvirt自动进行空检查时生成此代码是愚蠢的.

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

案件3呢?在什么情况下C#可以跳过空检查并生成一个调用?只有当:

  • 该调用是一个非虚方法调用,并且
  • C#已经知道接收器不是null

但是C#知道在new Foo().Bar()接收器中不能为null,因为如果是,那么构造就会抛出一个异常而我们永远不会接到电话!

编译器不够聪明,不能意识到变量只被分配了非空值.所以它生成一个安全的callvirt.

编译器可以写得那么聪明.编译器必须跟踪变量的赋值状态以进行明确的赋值检查.它还可以跟踪"被分配了可能为空的状态"状态,然后它将在两种情况下生成调用.但是编译器还不是那么聪明.