在mmkk的blog上看到一个关于Object.GetType()的观点,想作些补充,和各位分享(原文请见http://v-instru.com/blog/posts/220.aspx)。
由于所有的类都隐式继承Object,并且获得Object.GetType(),不过由于派生类可以new一个新的GetType()遮蔽Object.GetType(),比如:
public new Type GetType()
{
return typeof(System.String);?
}
这样的话如果instance想调用GetType()来获得精确类型的话得到将是System.String,而不是期望得到的,相反通过Type.GetType(string)这种形式可以绕过这个不确定因素,Type.GetType()是否应该是一种更好的习惯呢?
很显然,按照平时使用GetType()方法时的直观感觉,该方法在不同类型的对象实例上执行时将得到不同的结果,以面向对象的说法,这应该就是传说中的“多态”方法咯!而实际上,该方法并非虚方法,其“多态”的行为实际上是CLR类型系统的特殊实现所形成的,细节掠过,感兴趣的朋友可以参考一下SSCLI(即Rotor)中的源码。
因此,从理论的角度讲,要想得到真正的Object.GetType()的行为,应该只通过类型为Object的实例引用调用该方法;从实践的角度看,除非有特别的企图,正常的类型定义也不会去故意的“隐藏”Object默认的GetType()行为,所以一般用任何类型的实例引用调用GetType()也不会得到意外的结果。
至于说到底有什么原因需要提供一个全新的GetType()方法以隐藏默认的Object.GetType()行为,我还没想出来。而且就算提供了新的实现,也需要使用派生类的引用类型去调用才能获得这一新的变化。
有意思的是,如果你用Visual C# .NET编译后面附带的代码(在评论中),你会发现在编译出来的IL代码中,调用GetType()用的是callvirt指令(而不是我以为的call指令!两者区别由名称即可看出,这里就不赘述)!起初我以为这是C#编译器对GetType()方法的特殊支持,后来发现原来所有调用类实例方法的指令都是用callvirt而不是call。查过资料后才知道,原来是VC#.NET的编译器利用了callvirt指令的一个特殊功能,即在实际调用方法前首先验证this指针(即对象引用)的有效性,而这个特性是call指令所不具备的(但却是一个非常重要且实用的特性)。除此区别之外,call指令是使用类型的方法表寻找所需调用的方法入口,而callvirt是从实例的虚方法表开始寻找。但当使用callvirt调用一个非虚方法时,因为其肯定不存在于实例的虚方法表中,因此最终还是在类型的方法表找到所需调用的方法的入口,因此调用的还是所指定的非虚方法(但这样是否引入了额外的开销呢?有空再深入研习一下咯……噢,好象跑题了)
罗嗦半天,其实就是为了得到这样两个结论:第一,总是使用Object类型的对象引用调用GetType()方法以得到实例的真实类型(objectInstance.GetType()或((Object)anyTypeInstance).GetType());第二,既然编译器已经将覆盖基类成员这一操作列入警告级别,则该操作就是很有可能引入问题的操作,那么任何显式的压抑警告的做法(如使用new成员修饰符)都是值得反复思量并显式说明的。
至于说是否改用Type.GetType()是一种更好的习惯,我想这两个方法的名称虽然相同,但是目的却是不同的(因为前者是Object类的,而后者是Type类的。所属的类不同,方法的作用基础也就不同,含义也就不同了),一般也是很难简单换用的。不知mmkk是否认同我的以上看法呢?:)