首页 新闻 会员 周边

继承的一点疑问

0
悬赏园豆:80 [已解决问题] 解决于 2012-10-08 09:11

代码如下:

 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共有一份?

 

忘各位园友 指点

 

 

Qlin的主页 Qlin | 老鸟四级 | 园豆:2403
提问于:2012-09-29 10:07
< >
分享
最佳答案
0

第一个题目没有明白什么意思!

Chicken chicken = new Chicken()

2.字段表有一份,typeHandle指向一个方法表。但是托管堆中存着两个方法表

Bird birdC = new Chicken { Name = "野鸡", Color = "yellow" };

3.birdC是Bird的访问指针,只是规定了类型的访问权限问题。除了访问权限和第二个没什么区别。

收获园豆:30
51秒懂 | 菜鸟二级 |园豆:239 | 2012-09-29 15:54

Chicken对象中的 ypeHandle指针指向Loader Heap上的MethodTable,

那 Chicken的Type元数据中的MethodTable 有 两个 ShowName和两个ShowColor吗?

Qlin | 园豆:2403 (老鸟四级) | 2012-09-29 16:12

@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  可能会有比较深入的理解

51秒懂 | 园豆:239 (菜鸟二级) | 2012-09-29 16:22

@so lucky: 

这本书,最近在看,好像没提到这个方法创建的,

按照你的说法,Chicken的 Method Table中 ShowName 一份,ShowColor 有两份咯

Qlin | 园豆:2403 (老鸟四级) | 2012-09-29 16:43

@Qlin: 你QQ多少?

51秒懂 | 园豆:239 (菜鸟二级) | 2012-09-29 16:45

@so lucky: 

QQ:  514688217

Qlin | 园豆:2403 (老鸟四级) | 2012-09-29 16:52

@Qlin: ShowName由于子类使用的是override 所以在子类的方法表中只有一个槽位,而ShowColor则用的是new所以子类中新建了一个槽位,加上原本从父类方法表中复制的槽位,就有两个槽位

清風揚諰 | 园豆:197 (初学一级) | 2012-10-01 10:58

@清風揚諰: 

呵呵 今天在园子里找到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类中的所有虚方法在Derived类的Method Table中一一对应
  • Base类中的所有非虚方法在Derived类中的Method Table并没有拷贝(这一点回答了上面的第一个问题)
  • Derived类新增的虚方法都添加到继承自Base类的虚方法的后面
  • 如果Derived类override Base类的虚方法,它就将该方法指向自身的实现
  • 如果Derived类使用new关键字隐藏了Base类虚方法的实现,它就相当于增加了一个虚方法,而不是覆盖。

下面看看调用虚方法时如何实现多态,比如有这样一段代码

  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类的类,也是同样,利用这种机制就实现了多态。

  结论

  •   每个类型对应一个Method Table
  •   子类的Method Table中包含父类的所有虚方法,而不包含父类的非虚方法
  •   CLR根据对象找到它对应类型的method table,然后根据该虚方法在method table中的偏移量实现多态调用
Qlin | 园豆:2403 (老鸟四级) | 2013-01-22 14:05
其他回答(4)
0

看着真复杂  

首先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
收获园豆:15
┢┦偉 | 园豆:1240 (小虾三级) | 2012-09-29 10:32

1. Name 只有一份? 创建不是先创建父类,再创建子类吗?

先Bird中Name 创建了一份,然后 创建Chicken时,将父类中的Name隐藏(这个隐藏是时候意思?),创建自己的Name,真的只是一份?

2. 3.2说法错误? 字段是一份;方法表 是两份,不管是override还是new?

3. 3.1 ShowColor不是重写,是隐藏, 如果 Name、Color 字段是一份, 那应该已经赋值了,干嘛还是null?

支持(0) 反对(0) Qlin | 园豆:2403 (老鸟四级) | 2012-09-29 10:46

@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时调用的是自己类中未赋值的对象
支持(0) 反对(0) ┢┦偉 | 园豆:1240 (小虾三级) | 2012-09-29 10:55

@┢┦偉: 

谢谢你的回答,所以总结下来,new 还是override,都是两份,不管是方法还是字段。

脑子里还是这个疑问:我觉得 override 重写,应该是将 父类的方法重新写过,即覆盖了父类的方法,即 override的方法只有一份,不管子类还是父类,都是调用同一个方法。

有没有相关 证明 是两份呢? IL代码不会看...

支持(0) 反对(0) Qlin | 园豆:2403 (老鸟四级) | 2012-09-29 11:06

@Qlin: 是的  都有两份,只不过他们调用的对象不同而已

重写:new  父类调用父类    new 子类调用子类    例:new Bird()、new Chicken()

 无论前面是哪个对象,你只有看后面就知道调用的是谁了

隐藏:要看new前面  new前面是父类调用就是父类,是子类调用子类

支持(0) 反对(0) ┢┦偉 | 园豆:1240 (小虾三级) | 2012-09-29 11:13

@┢┦偉: 

有没有 什么证明一下, 我怎么 觉得 只有3个方法的指针

支持(0) 反对(0) Qlin | 园豆:2403 (老鸟四级) | 2012-09-29 14:00

@Qlin: 指针的问题我也不是很懂

支持(0) 反对(0) ┢┦偉 | 园豆:1240 (小虾三级) | 2012-09-29 15:52
1

1. 运行效果是一样的,编译期的区别就是你不用new的话会生成一句警告。

2. “会先创建父类,再创建自己”,这句话其实是不对的,因为只创建了一个Chicken类型的对象,只不过会先调用父类的构造函数(以及字段赋值),再进行子类的构造。

2.1 会有两份,准确的说,他们的数据会有两份,他们就是2个独立的字段,谈不上份不份的。

2.2 方法表跟实例没有关系,即使你没有创建实例,方法表也是存在的,它是类型信息的一部分。因此跟多少个实例,谁的实例也没什么关系,也谈不上“份”了。

3. 是的,更准确的说,是隐式转换,存在于子类和基类之间的隐式转换。

3.1 由于他们是独立的字段,因此你只赋值了子类的,父类的自然是null

3.2 刚才说了跟实例无关,方法表是客观存在于类型信息中的。ShowName这个方法,在Bird和Chicken两个类型的方法表中都存在。ShowColor也是。

收获园豆:10
水牛刀刀 | 园豆:6350 (大侠五级) | 2012-09-29 10:59

3。方法表 是跟 实例无关,是保存在 type元数据的方法表中,每个type的方法表不一样。

我想知道 Chicken的方法表是怎么样的? 是 两份(暂且这样叫),还是一份。跟 new 和 override有没有关系。

支持(0) 反对(0) Qlin | 园豆:2403 (老鸟四级) | 2012-09-29 11:12

@Qlin: Chicken的方法表里是有2个方法。其中一个是override的ShowName,另外一个虽然叫ShowColor,但是它叫阿猫阿狗都跟基类无关。

支持(0) 反对(0) 水牛刀刀 | 园豆:6350 (大侠五级) | 2012-09-29 11:14

@水牛刀刀: 

继承基类的所有成员,应该有4个方法,为什么基类的 两个方法没有继承,Chicken实例是 没有生成 指向基类方法的指针(暂且这样叫),Chicken的实例 是 4个、3个、还是2个 方法指针?

支持(0) 反对(0) Qlin | 园豆:2403 (老鸟四级) | 2012-09-29 11:32
0

水牛说的对。

字段是存在两分的。你可以通过类型转换来访问:

Bird mix = new Chicken();//mix.Name是访问Bird的
Chicken changed= (Chicken)mix ;//changed.Name是访问Chicken的
收获园豆:5
Ethan轻叹 | 园豆:996 (小虾三级) | 2012-09-29 11:05
0

你这个问题有点复杂,我就不按照你问的来说,自己组织语言了。

一、所有类型内部定义的方法和字段都是实际存在的,不会因为继承的原因而变少了,所以你上面问的“会有两份吗”是多问的,肯定每个类型一份,各自存储。

二、对于new关键字,有无是无所谓的,有了可以让编译器认为你是故意的,没有则会认为你是无意的,编译器要警告你,这里面可能会有运行时错误产生。

三、override和new有本质的区别,override会替换基类的方法,而new则不会,因此你的那个例子中输出“birdC.ShowName()”却不是调用Bird类的那个方法,因为被他的派生类改写了,而另一个“birdC.ShowColor();”则是调用Bird类的方法,它没有被new关键字改写。

四、即使使用了override关键字将基类方法替换了,也是可以访问基类的方法的,在派生类内部通过“base.”的方式即可访问,只是对外不可见,这说明通过override关键字改写的方法,也是存在2份的,基类和派生类各自一份独立维护,否则谈不上内部调用。

收获园豆:20
秦楼东 | 园豆:913 (小虾三级) | 2012-09-29 12:15

那你的意思是说  Chicken的实例,使用了 override的方法,其实是一个,不是两个了?

那Chicken 的实例里有 3个方法了引用了?

支持(0) 反对(0) Qlin | 园豆:2403 (老鸟四级) | 2012-09-29 13:15

@Qlin: 

前面那句没问题,因为用了override后,外面调用的时候只能使用被override后的方法,基类的方法访问不到了。后面那句就有问题了,因为外面访问不到不代表内部不能访问,我最后都说了,可以通过“base.”的方式访问了,因此在类的内部,4个方法都存在,被override的基类方法只是对外不可见,但实际是存在的。

如果你一定要纠结到内存分布上面,其实他们在IL里面是各自分布的,IL检查他们的继承关系,发现有继承关系存在时,然后确定是否要查找继承链,如果要查找,则查找父类的相关field、property、method等,如果不查找继承链,则只返回自身的。但自身绝对不会再次将父类的所有这些内容复写一遍,没必要也不值得。

支持(0) 反对(0) 秦楼东 | 园豆:913 (小虾三级) | 2012-09-29 15:50

@Qlin: 

看下面的回复,感觉你看书看傻了。我就没看过一本书,但是已经会用IL编写代码了。如果你直接尝试IL编程,这些原理根本很容易理解。C#语法糖是为了方便编程,基于C#的语法看问题,怎么可能看的清楚,至于什么指针,在.NET里面基本不谈,不要拿C++的思路来解释,.NET有更好的方式。

支持(0) 反对(0) 秦楼东 | 园豆:913 (小虾三级) | 2012-09-29 17:00
清除回答草稿
   您需要登录以后才能回答,未注册用户请先注册