我们先看下面一段程序:

  1. public class Father
  2. {
  3. public void Run0()
  4. {
  5. Console.WriteLine("Father.Run0");
  6. }
  7. }
  8. public class Son:Father
  9. {
  10. public void Run0()
  11. {
  12. Console.WriteLine("Son.Run0");
  13. }
  14. }
  15. class Program
  16. {
  17. static void Main(string[] args)
  18. {
  19. Father[] fatherList = new Father[2];
  20. fatherList[0] = new Father();
  21. fatherList[1] = new Son();
  22. fatherList[0].Run0();
  23. fatherList[1].Run0();
  24. }
  25. }

程序的运行结果是:
Father.Run0

Father.Run0

稍微细心的朋友可能发现在Son类的Run0方法下面有一段棕色的波浪线,当我们把鼠标放到该下划线上时,会看到下面的提示(编译程序时在程序的“输出”窗口也能看到这个警告):

“MethodDemo.Son.Run0()”隐藏了继承的成员“MethodDemo.Father.Run0()”。如果是有意隐藏,请使用关键字new。

如图:

然后我们再来第二个版本的Run方法,我们称之为Run1(),,与第一个版本的区别是在子类的同名同参(方法名相同,参数个数和参数顺序相同,下同)方法前面加上了一个new关键字,代码如下:

  1. public class Father
  2. {
  3. public void Run1()
  4. {
  5. Console.WriteLine("Father.Run1");
  6. }
  7. }
  8. public class Son:Father
  9. {
  10. public new void Run1()
  11. {
  12. Console.WriteLine("Son.Run1");
  13. }
  14. }
  15. class Program
  16. {
  17. static void Main(string[] args)
  18. {
  19. Father[] fatherList = new Father[2];
  20. fatherList[0] = new Father();
  21. fatherList[1] = new Son();
  22. fatherList[0].Run1();
  23. fatherList[1].Run1();
  24. }
  25. }

运行结果如下:

Father.Run1

Father.Run1

我们发现加上new关键字之后,程序的运行结果没有发生改变。也就是在C#中,如果在子类中有与父类同名同参的方法时,C#会隐式帮你在子类的方法前面添加一个new关键字。

我们再写第三个版本的Run方法,即Run2(),与第一个版本不同的是父类的Run2()方面前面加了一个virtual关键字,表示这是一个虚方法,子类的Run2()除了与第一个版本号不同之外(Run0()改成Run2())没有什么不同。

程序代码如下:

  1. public class Father
  2. {
  3. public virtual void Run2()
  4. {
  5. Console.WriteLine("Father.Run2");
  6. }
  7. }
  8. public class Son:Father
  9. {
  10. public void Run2()
  11. {
  12. Console.WriteLine("Son.Run2");
  13. }
  14. }
  15. class Program
  16. {
  17. static void Main(string[] args)
  18. {
  19. Father[] fatherList = new Father[2];
  20. fatherList[0] = new Father();
  21. fatherList[1] = new Son();
  22. fatherList[0].Run2();
  23. fatherList[1].Run2();
  24. }
  25. }

我们看看程序的运行效果:

Father.Run2

Father.Run2

程序运行效果与第一个仍然没有什么区别,不过这次子类(Son)的Run2()方法又出现了与Run方法的第一个版本(Run0())一样的警告:“MethodDemo.Son.Run2()”将隐藏继承的成员“MethodDemo.Father.Run2()”。若要使当前成员重写该实现,请添加关键字override。否则,添加关键字new。

我们再写第四个版本的Run方法,我们称之为Run3(),这次父类中Run3()的修饰符与第三个版本相比没有变化(依然是virtual,虚方法),而子类Son中的Run3()方法与第三个版本相比多了一个override修饰符(这次我们熟悉的那个棕色的波浪线警告没有了)。代码如下:

  1. public class Father
  2. {
  3. public virtual void Run3()
  4. {
  5. Console.WriteLine("Father.Run3");
  6. }
  7. }
  8. public class Son:Father
  9. {
  10. public override void Run3()
  11. {
  12. Console.WriteLine("Son.Run3");
  13. }
  14. }
  15. class Program
  16. {
  17. static void Main(string[] args)
  18. {
  19. Father[] fatherList = new Father[2];
  20. fatherList[0] = new Father();
  21. fatherList[1] = new Son();
  22. fatherList[0].Run3();
  23. fatherList[1].Run3();
  24. }
  25. }

程序的运行结果如下:

Father.Run3

Son.Run3

这次我们发现程序的运行结果与前面三次不一样了,这次尽管我们声明的对象类型都是Father类(Father数组装的自然都是Father类型的引用),但是因为实例化数组中第二个元素的时候调用了Son类的构造函数,也就是实例化了一个Father类的子类(我们知道子类可以当作父类来看待,他们之间是is a的关系,反之则不行),也就是说fatherList数组中的第二个元素的引用类型是Father类型,但它的实例类型确实Son类型。而在运行的时候,并不是根据我们的引用类型(引用类型是Father类型的)去调用该引用类型的方法,而是调用该引用类型所指向的实例的方法。

为什么会发生这些现象呢?这里要提到两个概念:早绑定(early binding)和晚绑定(Late binding)。这个术语出现在存在继承关系的基类和派生类中,它们同时定义了一个同名同参的方法。

早绑定:在编译的时候就已经确定了将来程序运行基类或是派生类的哪个方法。在编译代码的时候根据引用类型就决定了运行该引用类型中定义的方法,即基类的方法。这种方法运行效率高。

晚绑定:只有在运行的时候才能决定运行基类或者派生类中的哪个方法。运行的时候将根据该实际类型而不是引用类型来调用相关方法,即取决于我们new了什么样对象。也就是即使我们new一个Father类的子类Son的实例,而不管我们是用Father类的引用指向这个Son的实例,方法调用的时候依然是调用Son的方法,而不是Father类的同名方法。

如我们上面所见,为了实现晚绑定,C#引入了两个关键词virtual和override。和Java中不同,Java中一切方法都是虚方法,也就是在运行的时候,JVM会自动检测该引用的类型与实际类型是否一致(无论如何,该引用类型与实际类型之间存在着相等或者继承关系,这样才满足is a的关系),如果一致执行该类型中定义的方法;如果不一致则会检查该引用的实际类型是否具有同名同参方法,如果有则运行该实际类型的同名同参方法。这样会带来一个问题:每次运行的时候都会进行类型检查,这样会带来一定的性能消耗。而在C#中一切方法如果没有显示指明,都是非虚的。对于非虚的方法,CLR运行的时候并不会进行类型检查,而是直接运行该引用的类型中所定义的方法,即使这个引用所指向的实际类型是该引用类型的派生类,并且在派生类中存在着同名同参的方法,也不会运行派生类中定义的方法。这时,派生类中的方法隐藏了基类中的方法。

但是如果在基类中显示声明方法为虚方法,那么CLR在运行的时候会进行类型检查,如果发现引用类型和实际的对象类型不一致,就会检查派生类中是否覆盖(override)了基类中的方法,如果是,则会运行派生类中的方法,而不是引用类型中的方法。

new、virtual、override的更多相关文章

  1. [C#] 區分 abstract、virtual、override 和 new

    abstract.virtual.override和new是在類別的繼承關係中常用的四個修飾方法的關鍵字,在此略作總結. 1. 常用的中文名稱: n   abstract => 抽象方法. n  ...

  2. C#基础知识(base、this、new、override、abstract、virtual、static)

    前言 本文主要来讲解一下C#中,自己觉得掌握的不怎么样或者用的不多,不太熟悉的关键字,主要包括base.this.new.override.abstract.virtual以及针对static字段和s ...

  3. sealed、new、virtual、abstract与override 趣解

    1. sealed——“断子绝孙” 密封类不能被继承.密封方法可以重写基类中的方法,但其本身不能在任何派生类中进一步重写.当应用于 方法或属性时,sealed修饰符必须始终与override一起使用. ...

  4. C#基础知识系列七(base、this、new、override、abstract、virtual、static)

    前言 本文主要来讲解一下C#中,自己觉得掌握的不怎么样或者用的不多,不太熟悉的关键字,主要包括base.this.new.override.abstract.virtual以及针对static字段和s ...

  5. sealed、new、virtual、abstract与override 总结

    1. sealed——“断子绝孙” 密封类不能被继承.密封方法可以重写基类中的方法,但其本身不能在任何派生类中进一步重写.当应用于方法或属性时,sealed修饰符必须始终与override一起使用. ...

  6. abstract、override、new、virtual、sealed使用和示例

    abstract修饰类名为抽象类,修饰方法为抽象方法.如果一个类为抽象类,则这个类智能是其他某个类的基类.抽象方法在抽象类中没有函数体.抽象类中的抽象方法是没有方法体的,继承其的子类必须实现抽象类的抽 ...

  7. base、this、new、override、abstract、virtual、static

    前言 本文主要来讲解一下C#中,自己觉得掌握的不怎么样或者用的不多,不太熟悉的关键字,主要包括base.this.new.override.abstract.virtual以及针对static字段和s ...

  8. c#中abstract、override、new、virtual、sealed使用

    abstract     修饰类名为抽象类,修饰方法为抽象方法.如果一个类为抽象类,则这个类智能是其他某个类的基类.抽象方法在抽象类中没有函数体.抽象类中的抽象方法是没有方法体的,继承其的子类必须实现 ...

  9. c#中abstract、override、new、virtual、sealed使用和示例

    原文地址:http://blog.csdn.net/richerg85/article/details/7407544 abstract      修饰类名为抽象类,修饰方法为抽象方法.如果一个类为抽 ...

  10. Delphi 方法:overload、override、virtual、dynamic、abstract

    1.overload 在Pascal语法规则中,同一个UNIT里是不能存在两个同名的函数的,例如: function func(): Boolean; function func(const x: C ...

随机推荐

  1. 半硬化树脂PP的型号

    1080是PP半固化胶片的型号(perperg),还有7628,2116,2113,2112,1506等等型号,每种型号不一样代表其PP内部的玻纤布不一样,比如7628的玻纤布相对较粗.数值较小则玻纤 ...

  2. Java中的低级错误

    1.              不能用“==”比较两个字符串内容相等. 2.              对list做foreach循环时,循环代码中不能修改list的结构. 3.            ...

  3. [flask]分页显示列表

    添加分页支持的视图函数 app.py @app.route('/search') def search(): page = request.args.get('page', 1, type=int) ...

  4. 【flask】环境配置-python-dotenv的使用

    [自动发现程序实例] 一般来说,在执行flask run命令运行程序前,我们需要提供程序实例所在模块的位置 . Flask会自动探测程序实例,自动探测存在下面这些规则: 从当前目录寻找app.py和w ...

  5. React之defaultProps、propTypes

    1.新增知识点 /** React中的组件: 解决html 标签构建应用的不足. 使用组件的好处:把公共的功能单独抽离成一个文件作为一个组件,哪里里使用哪里引入. 父子组件:组件的相互调用中,我们把调 ...

  6. javascript-->getElementsByClass

        //通过类名获取元素    //obj->目标元素的上一层元素  cName->目标类名 tagName->目标的标签类型(可缺省)    function getEleme ...

  7. Day03:运算符和表达式 / 分支结构

    Java 运算符 计算机的最基本用途之一就是执行数学运算,作为一门计算机语言,Java也提供了一套丰富的运算符来操纵变量.我们可以把运算符分成以下几组: 算术运算符 关系运算符 位运算符 字符串运算符 ...

  8. 【VS开发】如何移植对话框?

    [VS开发]如何移植对话框? 标签:[VS开发] 问题描述:当开发好一个可视化界面的时候,想将其移植到另外的工程中,这个时候希望能够导出对话框资源,好直接在另一个工程中进行编辑,而不用再次编辑对话框上 ...

  9. layer最大话.最小化.还原回调方法

    layer.open({              type: 1,             title: ‘在线调试‘,           content: ‘这里是内容‘,            ...

  10. mysql如何下载历史版本?

    进入官网 www.mysql.com