调用非指定类的方法

时间:2016-01-26 14:16:03

标签: c# oop il

我怀疑这两个方面;

第一个;

        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

你能告诉我吗?

2 个答案:

答案 0 :(得分:20)

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

  

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

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

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

  

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

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

  

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

  

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

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

  

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

它不是虚拟的。

  

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

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

call和callvirt有两个不同之处。

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

  • callvirt 始终检查接收方是否为空,无论所调用的方法是否为虚拟。 call不检查接收器是否为空。使用null"这个"调用方法是合法的。通过电话。

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

  

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

即可。当您使用null接收器调用某些内容时,C# required 会崩溃。因此,在生成调用方法的代码时,C#有以下选择:

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

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

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

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

  • 该调用是非虚方法调用,
  • C#已经知道接收方不为空

但是C#知道在new Foo().Bar()中,接收者不能为空,因为如果是,那么构造会引发异常,我们永远不会接到电话!

编译器足够智能,以便意识到变量只被分配了非空值。所以它会生成一个安全的callvirt。

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

答案 1 :(得分:0)

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

唯一真正的区别在于,在第一种情况下,您将Test实例分配给变量,以便稍后可以访问它/在调试器中检查它。在第二种情况下,你不能这样做。

就逻辑而言,如果你以后不再对test做任何事情,除了第二种情况下的一个非常小的性能改进之外没有任何区别(这么小我无法想到任何实际的它可能会计数的情况。)