我对这两方面都有疑问;
第一;
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
您能告诉我一下吗?
这个问题在这里有点难找到,但我认为你要问的是:
为什么将新创建的引用分配给变量会导致编译器生成callvirt,但直接调用方法会生成call?
你非常善于观察,注意到了这种微妙的差异。
在回答您的问题之前,让我们先回答一些其他问题。
我应该相信编译器会生成好的代码吗?
一般来说是的。偶尔会出现代码生成错误,但这不是其中之一。
用callvirt调用非虚方法合法吗?
Yes.
用call调用虚方法合法吗?
是的,如果您尝试调用基类方法而不是派生类中的重写。但通常这种情况不会发生。
本例中调用的方法是否是虚拟的?
这不是虚拟的。
由于该方法不是虚拟的,因此可以使用 callvirt 或 call 来调用它。为什么编译器有时生成 callvirt,有时生成 call,而它可以一致地两次生成 callvirt 或调用两次?
现在我们来谈谈你问题中有趣的部分。
call 和 callvirt 之间有两个区别。
现在也许你明白这是怎么回事了。
每当对空引用接收器进行调用时,C# 是否需要因空取消引用异常而崩溃?
Yes。 C# 是required当您使用空接收器调用某些内容时会崩溃。因此,C# 在生成调用方法的代码时有以下选择:
- 情况 1:生成检查 null 的 IL,然后生成调用。
- 情况2:生成callvirt。
- 情况 3:生成调用,但不以空检查开始。
案例1简直就是愚蠢的。 IL 更大,因此占用更多磁盘空间,加载速度更慢,抖动速度也更慢。当 callvirt 自动执行空检查时生成此代码是愚蠢的。
案例2很聪明。 C# 编译器生成 callvirts,以便自动完成 null 检查。
现在案例 3 怎么样?什么情况下C#可以跳过空检查并生成调用?只有当:
- 该调用是非虚拟方法调用,并且
- C# 已经知道接收者不为空
但 C# 知道在new Foo().Bar()
接收者不能为空,因为如果是的话,构造就会抛出异常,并且我们永远不会调用!
编译器是not足够聪明,能够意识到该变量只被分配过非空值。所以它会生成一个 callvirt 以保证安全。
编译器could被写得那么聪明。编译器已经必须跟踪变量的赋值状态以进行明确的赋值检查。它还可以跟踪“被分配了可能为空的东西”状态,然后它会在这两种情况下生成调用。但编译器(还)还没有那么聪明。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)