代码如下:
public class Bird { public string Name; public string Color; public virtual void ShowName() { Console.WriteLine("Bird:{0}", Name); } public virtual void ShowColor() { Console.WriteLine("Bird:{0}", Color); } } public class Chicken : Bird { public new string Name; public string Color; public override void ShowName() { Console.WriteLine("Chicken:{0}", Name); } public new void ShowColor() { Console.WriteLine("Chicken:{0}", Color); } } static void Main(string[] args) { Bird bird = new Bird { Name = "凤凰", Color = "red" }; Chicken chicken = new Chicken { Name = "土鸡", Color = "gray" }; Bird birdC = new Chicken { Name = "野鸡", Color = "yellow" }; bird.ShowName(); bird.ShowColor(); chicken.ShowName(); chicken.ShowColor(); birdC.ShowName(); birdC.ShowColor(); Console.Read(); }
疑问:
1.用new 和不用new(缺省) 是一样的吗,如 Chicken 的 name 和color 字段?
2.Chicken chicken = new Chicken 内存分布问题,会先 创建父类,再创建自己。
2.1 : Name,Color 字段 有两份吗?一份是Bird的,一份是Chicken 的?
2.2:ShowName()方法表,会有两份吗,一份是Bird的,一份是Chicken的?因为重写,觉得是一份。
ShowColor方法表,应该有两份吧,因为 new了?? 对不对
3. Bird birdC = new Chicken { Name = "野鸡", Color = "yellow" };这句是类型转换
3.1由于Name,Color有两份,只赋值了Chicken的,Birdc中的Name,Color 没有赋值,空字符串为null,对不对?
3.2 可不可以这样理解,ShowName方法被重写,只有一份,Bird 和Chicken共有一份?
忘各位园友 指点
第一个题目没有明白什么意思!
Chicken chicken = new Chicken()
2.字段表有一份,typeHandle指向一个方法表。但是托管堆中存着两个方法表
Bird birdC = new Chicken { Name = "野鸡", Color = "yellow" };
3.birdC是Bird的访问指针,只是规定了类型的访问权限问题。除了访问权限和第二个没什么区别。
Chicken对象中的 ypeHandle指针指向Loader Heap上的MethodTable,
那 Chicken的Type元数据中的MethodTable 有 两个 ShowName和两个ShowColor吗?
@Qlin: 具体内部我还真不知道如何实现的 那个Method Table应该是动态Method Table。Method Table的创建是父类在前,子类在后,类Chicken生成方法列表时,首先将Bird的所有方法复制一份,然后和Chicken本身的方法列表做对比,如果有覆写的虚方法则以子类方法覆盖同名的父类方法,同时添加子类的新方法,从而创建完成Chicken的方法列表。
Chicken chicken = new Chicken() 有一个Method Table
Bird birdC = new Chicken { Name = "野鸡", Color = "yellow" };的typeHandle指向一个方法表
建议看一个你必须知道的.NET 可能会有比较深入的理解
@so lucky:
这本书,最近在看,好像没提到这个方法创建的,
按照你的说法,Chicken的 Method Table中 ShowName 一份,ShowColor 有两份咯
@Qlin: 你QQ多少?
@so lucky:
QQ: 514688217
@Qlin: ShowName由于子类使用的是override 所以在子类的方法表中只有一个槽位,而ShowColor则用的是new所以子类中新建了一个槽位,加上原本从父类方法表中复制的槽位,就有两个槽位
@清風揚諰:
呵呵 今天在园子里找到http://q.cnblogs.com/q/46069/
http://www.itstrike.cn/Question/CLR-how-to-achieve-polymorphic-virtual-method-calls-I
以记录下来,供参考。
public class Base { public virtual void VirtualFun1() { Console.WriteLine("Base.VirtualFun1"); } public void NoneVirtualFun1() { System.Console.WriteLine("Base.NoneVirtualFun1"); } public virtual void VirtualFun2() { System.Console.WriteLine("Base.VirtualFun2"); } public virtual void VirtualFun3() { System.Console.WriteLine("Base.VirtualFun3"); } } public class Derived : Base { public override void VirtualFun1() { Console.WriteLine("Derived.VirtualFun1"); } public new virtual void VirtualFun2() { System.Console.WriteLine("Derived.VirtualFun2"); } public virtual void VirtualFun4() { System.Console.WriteLine("Derived.VirtualFun4"); } }
Base类是基类,它包含三个虚方法VirtualFun1, VirtuaFun2, VirtualFun3和一个非虚方法NoneVirtualFun1。
Derived继承Base类,它重写了VirtualFun1虚方法,隐藏了Base类的VirtualFun2虚方法,然后又增加了VirtualFun4虚方法。
看看一个Base类的实例在内存中是怎样排布的:
Object Ref表示某Base实例的引用,它指向在GC Heap中分配的Base对象,这个对象可以分为三部分:同步块索引、类型指针和字段。主要来关注类型指针,它指向该类型的Method Table,这其实是在Load Heap中分配的Type类型对象,所有该类型的实例的类型指针都指向同一个Method Table(这里表示所有Base对象的类型指针都指向同一个Method Table)。
Method Table里面包含很多信息,这里关注有关Method这一区域,(如果想了解更详细的method table,请参考上面的文章)。
根据在Method Table里的信息,可以知道它包含9个Method(其实应该有个字段标示有多少个虚方法,这里就没画了)。接下来就是这些method,它分为两部 分,前面一部分是所有的虚方法,后面的是非虚方法。因为所有的类型都是继承自System.Object类,所以前四个方法是Object类的虚方法 (ToString, Equals, GetHashCode, Finalize),接着是Base类定义的三个虚方法(VirtualFun1, VirtualFun2, VirtualFun3),最后是Base类的非虚方法NoneVirtualFun1以及默认的构造函数。下面再来看看Derived类型的 Method Table:
仔细对比一下这两个Method Table,可以发现这样几个特点:
下面看看调用虚方法时如何实现多态,比如有这样一段代码
Base b = new Derived();
b.VirtualFun1();
编译后在我的机器上会生成这样的汇编代码:
mov ecx, esi
mov eax, dword ptr[ecx]
call dword ptr [eax + 3ch]
现在来解释这几句代码:mov ecx, esi 是将新构造的对象的地址保存在ecx寄存器中; mov eax, dword ptr[ecx] 表示ecx的值是一个指针(根据上面的图可以知道对象的头4个字节保存的是method table的地址),它将method table的地址保存到eax寄存器中,最后call dword ptr[eax + 3ch]。3ch表示偏移量,它表示该方法相对于该method table的偏址,是在该类型加载到load heap以后确定的。这样,由method table的地址加上method相对与method table的偏移量,就可以唯一确定一个方法。
这样在调用b.VirtualFun1(); 时,由于b是Derived类的实例,所以根据它指向的托管对象找到的method table是Derived类型的method table,就能正确调用该方法。因为Derived类中override了VirtualFun1这个虚方法,所以调用的是Derived类的实现,而 如果没有override基类的虚方法,它就指向基类的该方法的实现。
由此可以看出,CLR实现虚方法的机制主要是通过类型的method table加上该虚方法相对于method table的偏移量来确定调用具体方法的。一个虚方法在整个继承体系所有类型对应的method table中的偏移量是固定的,比如VirtualFunc1在Base类型的method table中的偏移量是3ch,它在Derived类型的method table中的偏移量也是3ch,如果还有继承自Derived类的类,也是同样,利用这种机制就实现了多态。
结论
看着真复杂
首先Name只有一份,是父类bird的,因为你在chicken类声明name时用了new 把name字段隐藏了,
也就是说 当你Bird b=new Chicken(){Name="Jack"}; b.Name的时候是访问的Bird类中的Name字段,
而不是访问的Chicken类Name字段 在说color
如果:Chicken c=new Chicken(){color="yellow"} c.color 访问的是Chicken类的color 而非父类字段
同理 Bird b=new Chicken(); b.ShowColor() 访问的是b自己的ShowColor
3.2的说法不对 ,应该说他们各有一份 Bird b=new Chicken(); b.ShowName() 访问的是Chicken类的
而 Bird b=new Chicken();b.ShowName() 是访问自己类中的
Chicken c=new Chicken(); c.ShowName() 访问的是自己类中的
记住 继承后用new 是把子类和父类同名的对象给隐藏,让父类看不到
回答3.1
Bird birdC = new Chicken { Name = "野鸡", Color = "yellow" };
birdC.ShowColor时 因为子类重写 所以父类的Color字段并没有值 ,输出当然是null
1. Name 只有一份? 创建不是先创建父类,再创建子类吗?
先Bird中Name 创建了一份,然后 创建Chicken时,将父类中的Name隐藏(这个隐藏是时候意思?),创建自己的Name,真的只是一份?
2. 3.2说法错误? 字段是一份;方法表 是两份,不管是override还是new?
3. 3.1 ShowColor不是重写,是隐藏, 如果 Name、Color 字段是一份, 那应该已经赋值了,干嘛还是null?
@Qlin:
Bird birdC = new Chicken()
birdC.Name
隐藏就是子类中和父类同名的对象并用new关键字修饰后,父类掉用同名对象时调用的是自己的对象
说错了 Name不是只有一份,因为Chicken c=new Chicken()的时候调用的是Chicken类的
所以是两份,各自有一份
回答3 3.1
Bird birdC = new Chicken { Name = "野鸡", Color = "yellow" };
你赋值的是chicken类中的color字段,覆盖了父类的字段,所以用birdC调用Color时调用的是自己类中未赋值的对象
@┢┦偉:
谢谢你的回答,所以总结下来,new 还是override,都是两份,不管是方法还是字段。
脑子里还是这个疑问:我觉得 override 重写,应该是将 父类的方法重新写过,即覆盖了父类的方法,即 override的方法只有一份,不管子类还是父类,都是调用同一个方法。
有没有相关 证明 是两份呢? IL代码不会看...
@Qlin: 是的 都有两份,只不过他们调用的对象不同而已
重写:new 父类调用父类 new 子类调用子类 例:new Bird()、new Chicken()
无论前面是哪个对象,你只有看后面就知道调用的是谁了
隐藏:要看new前面 new前面是父类调用就是父类,是子类调用子类
@┢┦偉:
有没有 什么证明一下, 我怎么 觉得 只有3个方法的指针
@Qlin: 指针的问题我也不是很懂
1. 运行效果是一样的,编译期的区别就是你不用new的话会生成一句警告。
2. “会先创建父类,再创建自己”,这句话其实是不对的,因为只创建了一个Chicken类型的对象,只不过会先调用父类的构造函数(以及字段赋值),再进行子类的构造。
2.1 会有两份,准确的说,他们的数据会有两份,他们就是2个独立的字段,谈不上份不份的。
2.2 方法表跟实例没有关系,即使你没有创建实例,方法表也是存在的,它是类型信息的一部分。因此跟多少个实例,谁的实例也没什么关系,也谈不上“份”了。
3. 是的,更准确的说,是隐式转换,存在于子类和基类之间的隐式转换。
3.1 由于他们是独立的字段,因此你只赋值了子类的,父类的自然是null
3.2 刚才说了跟实例无关,方法表是客观存在于类型信息中的。ShowName这个方法,在Bird和Chicken两个类型的方法表中都存在。ShowColor也是。
3。方法表 是跟 实例无关,是保存在 type元数据的方法表中,每个type的方法表不一样。
我想知道 Chicken的方法表是怎么样的? 是 两份(暂且这样叫),还是一份。跟 new 和 override有没有关系。
@Qlin: Chicken的方法表里是有2个方法。其中一个是override的ShowName,另外一个虽然叫ShowColor,但是它叫阿猫阿狗都跟基类无关。
@水牛刀刀:
继承基类的所有成员,应该有4个方法,为什么基类的 两个方法没有继承,Chicken实例是 没有生成 指向基类方法的指针(暂且这样叫),Chicken的实例 是 4个、3个、还是2个 方法指针?
水牛说的对。
字段是存在两分的。你可以通过类型转换来访问:
Bird mix = new Chicken();//mix.Name是访问Bird的 Chicken changed= (Chicken)mix ;//changed.Name是访问Chicken的
你这个问题有点复杂,我就不按照你问的来说,自己组织语言了。
一、所有类型内部定义的方法和字段都是实际存在的,不会因为继承的原因而变少了,所以你上面问的“会有两份吗”是多问的,肯定每个类型一份,各自存储。
二、对于new关键字,有无是无所谓的,有了可以让编译器认为你是故意的,没有则会认为你是无意的,编译器要警告你,这里面可能会有运行时错误产生。
三、override和new有本质的区别,override会替换基类的方法,而new则不会,因此你的那个例子中输出“birdC.ShowName()”却不是调用Bird类的那个方法,因为被他的派生类改写了,而另一个“birdC.ShowColor();”则是调用Bird类的方法,它没有被new关键字改写。
四、即使使用了override关键字将基类方法替换了,也是可以访问基类的方法的,在派生类内部通过“base.”的方式即可访问,只是对外不可见,这说明通过override关键字改写的方法,也是存在2份的,基类和派生类各自一份独立维护,否则谈不上内部调用。
那你的意思是说 Chicken的实例,使用了 override的方法,其实是一个,不是两个了?
那Chicken 的实例里有 3个方法了引用了?
@Qlin:
前面那句没问题,因为用了override后,外面调用的时候只能使用被override后的方法,基类的方法访问不到了。后面那句就有问题了,因为外面访问不到不代表内部不能访问,我最后都说了,可以通过“base.”的方式访问了,因此在类的内部,4个方法都存在,被override的基类方法只是对外不可见,但实际是存在的。
如果你一定要纠结到内存分布上面,其实他们在IL里面是各自分布的,IL检查他们的继承关系,发现有继承关系存在时,然后确定是否要查找继承链,如果要查找,则查找父类的相关field、property、method等,如果不查找继承链,则只返回自身的。但自身绝对不会再次将父类的所有这些内容复写一遍,没必要也不值得。
@Qlin:
看下面的回复,感觉你看书看傻了。我就没看过一本书,但是已经会用IL编写代码了。如果你直接尝试IL编程,这些原理根本很容易理解。C#语法糖是为了方便编程,基于C#的语法看问题,怎么可能看的清楚,至于什么指针,在.NET里面基本不谈,不要拿C++的思路来解释,.NET有更好的方式。