关于《你必须知道的.net》第六回的问题--IL和C#看似不一致的地方

本问题源于《你必须知道的.net》第六回,最近在学习anytao的大作《你必须知道的.net》,看到第六回

深入浅出关键字---base和this时,发现其中有个例子的C#代码和生成的IL似乎不一致。

1. 问题描述

主要就是其中base和this示例中的main函数。完整的代码请参考原博客深入浅出关键字---base和this

public class BaseThisTester

 {

	 public static void Main(string[] args)

	 {

		 Audi audi = new Audi();

		 audi[1] = "A6";

		 audi[2] = "A8";

		 Console.WriteLine(audi[1]);

		 audi.Run();

		 audi.ShowResult();

	 }

 }

这段代码对应的IL代码如下:

.method public hidebysig static void  Main(string[] args) cil managed

{

   .entrypoint

   // 代码大小       61 (0x3d)

   .maxstack  3

   .locals init (class Anytao.net.My_Must_net.Audi V_0)

   IL_0000:  nop

   //使用newobj指令创建新的对象,并调用构造函数初始化

   IL_0001:  newobj     instance void Anytao.net.My_Must_net.Audi::.ctor()

   IL_0006:  stloc.0

   IL_0007:  ldloc.0

   IL_0008:  ldc.i4.1

   IL_0009:  ldstr      "A6"

   IL_000e:  callvirt   instance void Anytao.net.My_Must_net.Vehicle::set_Item(int32,

                                                                               string)

   IL_0013:  nop

   IL_0014:  ldloc.0

   IL_0015:  ldc.i4.2

   IL_0016:  ldstr      "A8"

   IL_001b:  callvirt   instance void Anytao.net.My_Must_net.Vehicle::set_Item(int32,

                                                                               string)

   IL_0020:  nop

   IL_0021:  ldloc.0

   IL_0022:  ldc.i4.1

   IL_0023:  callvirt   instance string Anytao.net.My_Must_net.Vehicle::get_Item(int32)

   IL_0028:  call       void [mscorlib]System.Console::WriteLine(string)

   IL_002d:  nop

   IL_002e:  ldloc.0

   IL_002f:  callvirt   instance void Anytao.net.My_Must_net.Vehicle::Run()

   IL_0034:  nop

   IL_0035:  ldloc.0

   //base.ShowResult最终调用的是最高级父类Vehicle的方法,

   //而不是直接父类Car.ShowResult()方法,这是应该关注的

   IL_0036:  callvirt   instance void Anytao.net.My_Must_net.Vehicle::ShowResult()

   IL_003b:  nop

   IL_003c:  ret

} // end of method BaseThisTester::Main

问题就是最后的一步,也是作者在IL中特意加注释说明的那步 audi.ShowResult();

这步代码应该是调用Audi这个类的ShowResult()方法,为什么IL中会调用最终的基类Vehicle中的方法呢???

在这篇博客下面的评论中有些读者已经提出了这个疑问,作者的解释如下:

而IL分析中关于访问Vehicle::ShowResult的分析,是基于在Audi父类的ShowResult中有base的向上访问,因此最终会追溯到最高级父类,这是原因所在。
关于多层访问的描述有些欠妥,谢谢讨论,我考虑考虑,及时修订。

作者解释的原因似乎是由于Audi类的ShowResult()方法中有base.ShowResult(); 所以就一直追朔到了最高级父类。

如果我们将Audi类的ShowResult()方法中的base.ShowResult(); 注释掉,那么IL中是否还是调用基类Vehicle中的ShowResult()方法呢???

答案是肯定的,即使注释掉这个代码,IL还是和上面一样,没有任何改变。这也是原博客中评论的第54楼的疑问。

2. 原因分析

刚开始看到这个问题的时候,我也是很迷惑,明明Audi类的ShowResult()方法已经override其父类的方法了,为什么IL中还会调用其父类的方法呢?

后来看了《CLR via C#》这本书,对IL中的这种写法总算有了个合理的解释。至于我的理解对不对,欢各位指教!!!!

首先CLR中基类和子类的关系如下图:

捕获

子类的方法表中不再有父类已经定义的方法了。

所以本例的三个类的方法表如下:

捕获

子类Audi和Car除了构造函数,没有自己定义的新函数。

同时我们也可以看出 override 只是覆盖父类的方法,不能算是新的方法。

所以在上面的IL中,调用的是Vehicle类的ShowResult()虚方法,只是在实际运行时JIT根据调用此方法的类型,编译相应的代码。

本例的调用此方法的类型即为Audi类。

为了验证上面的想法,我们可以将Audi类中ShowResult()方法的签名改为public new void ShowResult()。

将原先的override关键字改为new关键字。new关键字表示隐藏父类的同名方法,相当于子类新增了一个方法,与override覆盖基类的方法不同。

所以改成Audi类中ShowResult()方法的签名改为public new void ShowResult()后,IL中应该调用Audi类中ShowResult()方法。

下面是修改Audi类后新的Main函数IL代码,与预想的一致。

.method public static hidebysig 

	void Main (

		string[] args

	) cil managed 

{

	// Method begins at RVA 0x216c

	// Code size 68 (0x44)

	.maxstack 3

	.entrypoint

	.locals init (

		[0] class Anytao.net.My_Must_net.Audi audi

	)



	IL_0000: nop

	IL_0001: newobj instance void Anytao.net.My_Must_net.Audi::.ctor()

	IL_0006: stloc.0

	IL_0007: ldloc.0

	IL_0008: ldc.i4.1

	IL_0009: ldstr "A6"

	IL_000e: callvirt instance void Anytao.net.My_Must_net.Vehicle::set_Item(int32, string)

	IL_0013: nop

	IL_0014: ldloc.0

	IL_0015: ldc.i4.2

	IL_0016: ldstr "A8"

	IL_001b: callvirt instance void Anytao.net.My_Must_net.Vehicle::set_Item(int32, string)

	IL_0020: nop

	IL_0021: ldloc.0

	IL_0022: ldc.i4.1

	IL_0023: callvirt instance string Anytao.net.My_Must_net.Vehicle::get_Item(int32)

	IL_0028: call void [mscorlib]System.Console::WriteLine(string)

	IL_002d: nop

	IL_002e: ldloc.0

	IL_002f: callvirt instance void Anytao.net.My_Must_net.Vehicle::Run()

	IL_0034: nop

	IL_0035: ldloc.0

	IL_0036: callvirt instance void Anytao.net.My_Must_net.Audi::ShowResult()

	IL_003b: nop

	IL_003c: ldc.i4.1

	IL_003d: call valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey(bool)

	IL_0042: pop

	IL_0043: ret

} // End of method BaseThisTester.Main

3. 结论

通过以上的分析,我觉得IL虽然比c#要更“底层”一些,但还是隐藏了一些CLR的东西。在研究CLR的时候,如果能将IL和C#中看似矛盾的地方都弄清楚,可能能更进一步的理解CLR的原理。也能够对C#语言本身的运行机制有更深刻的理解。

你可能感兴趣的:(.net)