对C# 继承的理解
发表于2018-10-11
这篇文章主要分享的是我对C#中继承的理解。继承是可用传递的 , 子类是对父类的扩展 , 必须继承父类的方法,同时在子类中添加新方法。
子类可用调用父类的公用方法和字段 , 而父类不能调用子类的成员。
子类不仅继承了父类的共有成员 , 同时也继承了父类的私有成员 , 只是在子类中不能被访问。
继承的三个关键字:
- abstract 抽象:用来限定类时,类中的方法不能有方法实体;用来限定方法时,同样也不能有方法实体,并且在子类中必须完成方法实体,除非子类继续使用抽象方法。
- virtual:用来指示类中的方法可以被子类的同名方法覆盖或者共存,覆盖时,子类中使用override关键字;共存时,使用new 关键字。
- new:在子类方法中的用法是指子类中与父类存在的同名方法在方法列表敏感词存,并不覆盖父类的方法,这就达到了多态机制下并非一味覆盖的效果。
Swallow:Bird Bird:Animal Animal:Object
在创建 Swallow 对象的过程中,首先创建的是 Swallow 类里面的字段 , 字段创建完毕后 , 接着会创建其父类 Bird, 并为其字段分配存储空间,然而 Bird 继承 Animal ,在内存中也为 Animal 类中的字段分配存储空间 , 这样依次寻找到最终的父类的 Object 为止。在对象创建的过程中是按照顺序完成了对整个父类及其本身字段的内存创建,并且字段的存储顺序是由上到下的排列, Object 类的字段排在最前面。如果父类和子类出现了同名字段 , 则在子类对象创建时,编译器会自动认为这是两个不同的字段加以区分。
Swallow 生成方法列表时,首先将父类 Bird 的所有方法拷贝一份,然后和 Swallow 本身的方法进行对比,如果在子类中有 override 覆盖父类的同名方法,则用子类的方法代替父类的方法,这样就创建完成Swallow的方法列表。这种创建过程也是逐层递归到Object类,并且方法列表中也是按照顺序排列的。
Bird bird=new Swallow(); 这种情况下bird.Show();应该调用那个方法?有两个原则,是。NET专门解决这个问题。如下:
- 关注对象创建原则:调用子类还是父类的方法,取决于创建的对象是子类对象还是父类对象,而不是它的引用类型。
- 执行就近原则:对于同名的方法或者字段,编译器是按照顺序查找来引用的,也就是首先访问距离它最近的字段或者方法。
注意:ovveride重写父类的方法是在地址列表中覆盖了父类的方法,所以如果最终运行的是子类的对象,则执行了子类中的方法(因为覆盖了父类方法的地址,第一个原则)。
new是在子类中定义了与父类同名的方法,要隐藏父类的同名方法,在编译器的时候其实是为父类和子类的两个方法分配了地址,只不过父类方法排在前面,而子类方法排在后面,就近原则根据定义的类型来决定,这样根据就近原则,如果定义的对象类型为父类,则就近找到父类的方法执行,而如果定义在子类的类型,则就近找到子类的方法执行。
简单的测试逻辑代码如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ExtendProject { public class Animal : Object { private int id; public int Id { get { return this.id; } set { this.id = value; } } private string color; public virtual string Color { get { return this.color; } set { this.color = value; } } public void Show() { Console.WriteLine("Animal: id:" + this.id + ",color:" + this.color); ShowMySelf(); } private void ShowMySelf() { Console.WriteLine("AnimalShowSelf: id:" + this.id + ",color:" + this.color); } } } using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ExtendProject { public class Bird : Animal { private int id; public new int Id { get { return id; } set { id = value; } } new public virtual void Show() { Console.WriteLine( "Bird: id:" + Id+ ",color:" + Color); ShowMySelf(); } private void ShowMySelf() { Console.WriteLine("BirdShowSelf: id:" + this.id + ",color:" + Color); } } } using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ExtendProject { public class Swallow : Bird { private int id; public new int Id { get { return this.id; } set { this.id = value; } } private string color; public override string Color { get { return this.color; } set { this.color = value; } } public override void Show() { Console.WriteLine("Swallow: id:" + Id + ",color:" + this.color); ShowMySelf(); } private void ShowMySelf() { Console.WriteLine("SwallowShowSelf: id:" + this.id + ",color:" + this.color); } } } using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ExtendProject { class Program { static void Main(string[] args) { Animal animal = new Bird(); animal.Color = "blue"; animal.Id =10; animal.Show(); Console.WriteLine(); Bird bird = new Swallow(); bird.Color = "red"; bird.Id = 12; bird.Show(); Console.WriteLine(); animal = new Swallow(); animal.Color = "black"; animal.Id = 11; animal.Show(); Console.WriteLine(); } } }
在这里画一下内存分布图。
1、首先创建 Swallow 里面的字段 ( 对于属性而言其本质就是一种方法 , 有 C# 编译器自动实现 , 实现的结果就是 get_Id(),set_Id(int value) 这种最终的方法 过程请自行查看 CLR via C# 第三版或者第四版 这里不再多说 ), 最后找到 object 但是里面没有字段 , 这里就不进行创建。
类中的字段创建完毕后 , 进行方法的创建。首先生成 Swallow 的方法列表时 , 先将其父类 Bird 的方法 copy 一份。 然后进行两者比对:
Swallow 中的方法有:
new public int get_Id(); new public set_Id(int value); public override string get_Color(); public override set_Color(string value); public override Show(); private void ShowMySelf();
Bird 中的方法有:
new public int get_Id(); new public set_Id(int value); new public virtual void Show(); private void ShowMySelf();
下图为内存中创建的图
2、然后开始和父类 Bird 中的方法进行比对。
new public int get_Id(); new 表示隐藏 , 共存 但是内存中有父类和子类的 get_Id
new public void set_Id();new 表示隐藏 , 共存 但是内存中有父类和子类的 set_Id
public override string get_Color(); 父类不存在就创建子类的
public override set_Color(string value); 父类不存在就创建子类的
public override Show(); override 表示覆盖 覆盖父类中 show 方法,此时 Bird 在内存中的 Show 方法便指向了子类 Swallow 中的 show 方法 ( 下图中加粗线条显示 )
showMySelf 是私有的 , 在内存中同时存在。
这样创建完毕后内存如下图
然后开始 Bird 方法的创建 , 因为内存中有了 Bird 的方法 , 就直接创建 Animal 方法
Bird 中的方法有:
new public int get_Id(); new public set_Id(int value); new public virtual void Show(); private void ShowMySelf();
Animal 的方法有 :
public int get_Id(); public set_Id(int value); public virtual string get_Color(); public virtual set_Color(string value); public void Show(); private void ShowMySelf();
按照上面的对比方法后两者 (Animal 和 Bird) 内存中的状态为
最后内存中完整的状态如下图:
然后自己根据内存中的状态 进行分析调用程序即可。在C#中调用父类记得使用base,逐个调试你会得到更多信息。
注意子类只拷贝父类中的方法,如果父类中没有对应的方法,才会向上查找,直至找到object类。来自:https://blog.csdn.net/u010533180/article/details/52709684