关于《你必须知道的.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#语言本身的运行机制有更深刻的理解。



本文转自wang_yb博客园博客,原文链接:http://www.cnblogs.com/wang_yb/archive/2011/05/15/2046886.html,如需转载请自行联系原作者

目录
相关文章
|
存储 开发框架 自然语言处理
【.Net底层剖析】3.用IL来理解属性
【.Net底层剖析】3.用IL来理解属性
136 0
【.Net底层剖析】3.用IL来理解属性
【深入浅出.Net IL】1.一个For循环引发的IL
【深入浅出.Net IL】1.一个For循环引发的IL
111 0
【深入浅出.Net IL】1.一个For循环引发的IL
|
.NET C# C++
详解.NET IL代码(一)
  本文主要介绍IL代码,内容大部分来自网上,进行整理合并的。 一、IL简介  为什么要了解IL代码?   如果想学好.NET,IL是必须的基础,IL代码是.NET运行的基础,当我们对运行结果有异议的时候,可以通过IL代码透过表面看本质;IL也是更好理解、认识CLR的基础;大量的实例分析是以IL为基础的,所以了解IL,是读懂他人代码的必备基础,同时自己也可以获得潜移默化的提高;  什么是IL?   IL是.NET框架中中间语言(Intermediate Language)的缩写。
2101 0
|
Windows
一起谈.NET技术,如何通过ildasm/ilasm修改assembly的IL代码
  这段时间为跟踪一个Bug而焦头烂额,最后发现是Framework的问题,这让人多少有些绝望。所以到微软论坛提了个帖子,希望能得到些帮助。虽然论坛智能到能够判断楼主是否是MSDN订阅用户,以便尽快解决(传说MSDN订阅用户的问题能在两天内得到回复的,当时还很得意公司为我们购买的MSDN订阅账号),但得到的回复是“Could you file a bug report for this issue through Connect?”,绝望之后的又一次寒心啊。
967 0
|
.NET
一起谈.NET技术,关于Expression Tree和IL Emit的所谓的"性能差别"
  昨天写了《三种属性操作性能比较》,有个网友写信问我一个问题:从性能上看,Expression Tree和IL Emit孰优孰劣?虽然我在回信中作了简单的回答,但不知道这个网友是否懂我的意思。反正今天呆在家里也没事儿,干脆再就这个话题再写一篇文章。
852 0
|
Windows
如何通过ildas“.NET技术”m/ilasm修改assembly的IL代码
  这段时间为跟踪一个Bug而焦头烂额,最后发现是Framework的问题,这让人多少有些绝望。所以到微软论坛提了个帖子,希望能得到些帮助。虽然论坛智能到能够判断楼主是否是MSDN订阅用户,以便尽快解决(传说MSDN订阅用户的问题能在两天内得到回复的,当时还很得意公司为我们购买的MSDN订阅账号),但得到的回复是“Could you file a bug report for this issue through Connect?”,绝望之后的又一次寒心啊。
985 0
|
Windows
如何通过ildasm/ilasm修改assem“.NET研究”bly的IL代码
  这段时间为跟踪一个Bug而焦头烂额,最后发现是Framework的问题,这让人多少有些绝望。所以到微软论坛提了个帖子,希望能得到些帮助。虽然论坛智能到能够判断楼主是否是MSDN订阅用户,以便尽快解决(传说MSDN订阅用户的问题能在两天内得到回复的,当时还很得意公司为我们购买的MSDN订阅账号),但得到的回复是“Could you file a bug report for this issue through Connect?”,绝望之后的又一次寒心啊。
1154 0
|
.NET
关于Expression Tree和IL Emit的所谓的"性能差别&quot“.NET研究”;
  昨天写了《三种属性操作性能比较》,有个网友写信问我一个问题:从性能上看,Expression Tree和IL Emit孰优孰劣?虽然我在回信中作了简单的回答,但不知道这个网友是否懂我的意思。反正今天呆在家里也没事儿,干脆再就这个话题再写一篇文章。
743 0
|
C# 编译器 C++
浅谈.NET编译时注入(C#-->IL)
原文:浅谈.NET编译时注入(C#-->IL)      .NET是一门多语言平台,这是我们所众所周知的,其实现原理在于因为了MSIL(微软中间语言)的一种代码指令平台。所以.NET语言的编译就分为了两部分,从语言到MSIL的编译(我喜欢称为预编译),和运行时的从MSIL到本地指令,即时编译(JIT)。
968 0