Child类调用GrandFather类方法引发的思考
(原文发表于CSDN我的Blog:http://blog.csdn.net/happyhippy/archive/2006/10/02/1317830.aspx)
昨天在某论坛上看到这个问题,觉得有点意思,就贴过来,顺便贴下我对该问题的思考。
具体问题是这样的:
class GrandFatherClass
{
public virtual void Func() {}
}
Father类重写了这个方法
class FatherClass:GrandFatherClass
{
public override void Func() {}
}
现在Child类想直接调用GrandFather类中的Func要怎么做?
class ChildClass:FatherClass
{
public void OtherFunc()
{
((GrandFatherClass)this).Func();//不能调用GrandFatherClass中的Func().
//GrandFatherClass.Func()如何调用?
}
}
我用ILDasm工具反汇编上面代码编译生成的程序集,得到如下MSIL代码:
{
// 代码大小 11 (0xb)
.maxstack 8
IL_0000: ldstr "GrandFather"
IL_0005: call void [mscorlib]System.Console::WriteLine(string)
IL_000a: ret
} // end of method GrandFatherClass::Func
.method public hidebysig virtual instance void Func() cil managed
{
// 代码大小 11 (0xb)
.maxstack 8
IL_0000: ldstr "Father"
IL_0005: call void [mscorlib]System.Console::WriteLine(string)
IL_000a: ret
} // end of method FatherClass::Func
.method public hidebysig instance void OtherFunc() cil managed
{
// 代码大小 7 (0x7)
.maxstack 8
IL_0000: ldarg.0
IL_0001: callvirt instance void MyProject.GrandFatherClass::Func()
IL_0006: ret
} // end of method ChildClass::OtherFunc
我查了下MSDN,找到了这句话:“调用虚方法时,将为重写成员检查该对象的运行时类型。将调用大部分派生类中的该重写成员,如果没有派生类重写该成员,则它可能是原始成员。”也就是说,即使我们在ChildClass中执行((GrandFatherClass)this).Func()(从IL代码中我们也可以看到是在调用GrandFather的Func()方法),CLR检查到对象的运行时类型为ChildClass,而且ChildClass有重写了自己GrandFatherClass中的Func()(从FatherClass中继承而来),所以该语句仍会调用从FatherClass继承而来的Func()方法。
这里只是从表象上解释了原因,下面这篇《深入探索.NET框架内部了解CLR如何创建运行时对象》则从底层剖析了CLR对象模型,理解了CLR对象模型,我们就可以更加清楚地理解CLR中的方法分派(Dispatch)机制。
用IL和方法表布局来解释如上代码中的行为就是:在方法表中,CLR赋予每个虚方法一个方法槽(Slot),它将包含一个指向方法代码的指针(实际上是通过MethodDesc间接来指向该地址,上面这篇《深入探索……》里面将得比较清楚了,我不再赘述);FatherClass重写了GrandFather的Func()虚方法,它替换被覆盖的虚方法(Func),Func方法槽指向FatherClass实现的Func方法的地址。虚分派总是通过一个固定的槽编号Func()发生,和方法表指针在特定的类(类型)实现层次无关。在方法表布局时,类加载器用覆盖的子类的实现FatherCalss.Func()代替父类的实现GrandFather.Func()。结果,对父对象的方法调用被分派到子对象的实现。
下图是运行时对ChildClass中OtherFunc()反汇编得到的结果(可在调试时通过在命令窗口中输入disasm,可以查看IA-32汇编指令):
从图中我们可以看到,调用一个虚方法要执行三条IA-32汇编指令:
mov ecx,esi ;将目标对象的引用(在这里是this)存储在IA-32 ecx寄存器中
mov eax,dword ptr[ecx] ;这是针对虚方法调用的指令,将对象的类型句柄存储在eax寄存器中
call dword ptr [eax+offset] ;通过对象的类型句柄和方法在方法表中的偏移量来定位目标方法的实际地址
从图中我们也可以看到,不论我们执行((FatherClass)this).Func()还是执行((GrandFatherClass)this).Func(),都是在调用偏移量为38h所指向的方法(ChildClass的Func()只有这一个插槽)。
所以按照上面继承/重写的写法,不能实现调用GrandFatherClass中的Func()。要实现调用GrandFather中的方法,可按如下两种方法:
法一:
{
public virtual void Func() { Console.WriteLine("GrandFather"); }
}
class FatherClass : GrandFatherClass
{
public new virtual void Func() { Console.WriteLine("Father"); }
//加不加关键字new都没有关系,这里加new的作用只是消除编译器警告信息,不会对生成的IL代码产生任何影响。
}
class ChildClass : FatherClass
{
public void OtherFunc()
{
((GrandFatherClass)this).Func();
}
}
//对应的MSIL代码:
.method public hidebysig newslot virtual instance void Func() cil managed
{
// 代码大小 11 (0xb)
.maxstack 8
IL_0000: ldstr "GrandFather"
IL_0005: call void [mscorlib]System.Console::WriteLine(string)
IL_000a: ret
} // end of method GrandFatherClass::Func
.method public hidebysig newslot virtual instance void Func() cil managed
{
// 代码大小 11 (0xb)
.maxstack 8
IL_0000: ldstr "Father"
IL_0005: call void [mscorlib]System.Console::WriteLine(string)
IL_000a: ret
} // end of method FatherClass::Func
.method public hidebysig instance void OtherFunc() cil managed
{
// 代码大小 7 (0x7)
.maxstack 8
IL_0000: ldarg.0
IL_0001: callvirt instance void MyProject.GrandFatherClass::Func()//这里生成的是callvirt调用命令
IL_0006: ret
} // end of method ChildClass::OtherFunc
从IL代码中我们可以看到,FatherClass::Func上有应用newslot标示,CLR赋予被申明为newslot的虚方法一个新的methodoffset,所以这里并没有覆盖GrandFatherClass::Func(),而在FatherClass的方法表中的方法槽表(Method Slot Table)中,也同时存在两个Func()槽,一个是继承而来的,其指向GrandFather的Func()实现,另一个槽执行自身的实现。而ChildClass继承自FatherClass,所以ChildClass中的方法表中,也有两个Func()槽。在下图中,我们也可以看到,这两个方法在方法表中偏移量,一个为38H,另一个为3CH。
法二:
...{
public void Func() ...{ Console.WriteLine("GrandFather"); }
}
class FatherClass : GrandFatherClass
...{
public new void Func() ...{ Console.WriteLine("Father"); }
}
class ChildClass : FatherClass
...{
public void OtherFunc()
...{
((GrandFatherClass)this).Func();
}
}
//MSIL:
.method public hidebysig instance void Func() cil managed
...{
// 代码大小 11 (0xb)
.maxstack 8
IL_0000: ldstr "GrandFather"
IL_0005: call void [mscorlib]System.Console::WriteLine(string)
IL_000a: ret
} // end of method GrandFatherClass::Func
.method public hidebysig instance void Func() cil managed
...{
// 代码大小 11 (0xb)
.maxstack 8
IL_0000: ldstr "Father"
IL_0005: call void [mscorlib]System.Console::WriteLine(string)
IL_000a: ret
} // end of method FatherClass::Func
.method public hidebysig instance void OtherFunc() cil managed
...{
// 代码大小 7 (0x7)
.maxstack 8
IL_0000: ldarg.0
IL_0001: call instance void MyProject.GrandFatherClass::Func()//这里生成的是call调用命令
IL_0006: ret
} // end of method ChildClass::OtherFunc
仔细观察IL代码,我们会发现这里生成的是call指令(而前面两个调用虚方法时生成的是callvirt指令),IL中的call指令生成2条IA-32汇编指令:
mov ecx,esi ;把目标对象的引用放进ecx寄存器
call methodAddress ;直接调用methodAddress指向的目标方法
另外:FatherClass既然已经重写了其父类GrandFather中的Viturl方法Func(),而其子类ChildClass却要拒绝接受其重写的Func(),感觉这种继承体系本身就存在一些问题,可以考虑重构一下该继承体系,具体可参考Martin Fowler的《重构-改善既有代码的设计》中Refused Bequest(被拒绝的遗赠)一节。
Child类调用GrandFather类方法引发的思考相关推荐
- 一次线上Redis类转换异常排查引发的思考
之前同事反馈说线上遇到Redis反序列化异常问题,异常如下: XxxClass1 cannot be cast to XxxClass2 已知信息如下: 该异常不是必现的,偶尔才会出现: 出现该异常后 ...
- GDScript:关于派生类调用基类方法的一个注意事项
对于普通方法,在派生类中用"."+"方法名"的方式调用基类的方法,例如: 在基类中定义了一个do_something方法 #基类 func do_somethi ...
- python类方法以及类调用实例方法的理解
classmethod类方法 1) 在python中.类方法 @classmethod 是一个函数修饰符,它表示接下来的是一个类方法,而对于平常我们见到的则叫做实例方法. 类方法的第一个参数cls,而 ...
- Java——this关键字(调用本类属性、调用本类方法、表示当前对象)
目录 1.调用本类属性 2.调用本类方法 3.表示当前对象 this是一个非常灵活的关键字,不会明确表示一个固定概念,比如int,它就是表示一个整型. 1.调用本类属性 类中有许多成员,大部分情况下类 ...
- Spring之LoadTimeWeaver——一个需求引发的思考---转
原文地址:http://www.myexception.cn/software-architecture-design/602651.html Spring之LoadTimeWeaver--一个需求引 ...
- 由「Metaspace容量不足触发CMS GC」从而引发的思考
转载自 由「Metaspace容量不足触发CMS GC」从而引发的思考 某天早上,毛老师在群里问「cat 上怎么看 gc」. 好好的一个群 看到有 GC 的问题,立马做出小鸡搓手状. 之后毛老师发来 ...
- Octavia API接口慢问题排查引发的思考
女主宣言 文本梳理了Octavia API接口访问慢问题的排查过程和解决方案,并对排查过程中涉及到的相关知识点进行了梳理,希望日后遇到类似的问题可以有所借鉴和参考. PS:丰富的一线技术.多元化的表现 ...
- MySQL:由USE DB堵塞故障引发的思考
遇到故障,我们往往想的是如何解决这个故障,而不是从故障的根本去思考出现这个故障的原因?这样的结果,只能使我们得到了鱼,失去了渔.今天,我们就来分享一个由USE DB堵塞故障引发的思考案例. 故障描述 ...
- 【汇编语言与计算机系统结构笔记01】x86/MIPS/ARM指令集概述与特性,一篇HPCA引发的思考(商业生态的决定性作用)
资源Bilibili AV46914471 + AV57921488 汇编语言与计算机系统结构 清华大学 张悠慧 本次笔记内容: 01.汇编语言与计算机系统结构 02.汇编基础知识--指令集综述 文章 ...
最新文章
- IDEA2021全局配置maven
- ST17H26代码优化的疑问
- VS.Net 2005 Beta2连接Team Foundation Server的问题
- [html] html5中的meta标签robots有什么作用?
- mac 串口调试工具_MACamp;串口调试
- Enterprise Library v3 初步开发计划
- 力算未来丨一张图看懂华为“鲲鹏+昇腾”双引擎
- java contains 通配符_java 泛型通配符 extends, super
- backbone学习笔记:集合(Collection)
- 教你正确说话的6个玄机
- MindManager 2020注册机下载
- 用友凭证打印故障解决
- 9106w android7,三星n9106w官方原版固件rom刷机包_三星n9106w系统线刷包
- 蓝桥杯 算法提高 盾神与条状项链
- 北伦敦德比桑切斯为枪手扳平比分
- 【容器】Podman容器快速上手
- 印刷行业中过UV是什么意思?
- 【Free5GC】test.sh脚本测试流程
- MATLAB根据正态分布样本计算概率密度函数,生成服从样本正态分布的随机数
- 「UG/NX」BlockUI 标签/位图Label