我们先看下面一段程序:

  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. [VBA]指定列求和

    ##指定列求和 需求: 求和:列为“销售金额”的数值 Sub 求和()Dim i As Integer, j As IntegerFor i = 3 To 56For j = 15 To 81 Ste ...

  2. 四十六:数据库之Flask-SQLAlchemy的使用

    一:连接数据库1.安装:pip install flask-sqlalchemy2.将数据库信息更新到app.config['SQLALCHEMY_DATABASE_URI']3.使用flask_sq ...

  3. JSON中文处理类实例

    $array = array( 'Name'=>'络恩', 'Age'=>24); $post=my_json_encode($array); // 这个函数是判断版本,如果是搞版本的则直 ...

  4. 车牌识别1:License Plate Detection and Recognition in Unconstrained Scenarios阅读笔记

    一.WHAT 论文下载地址:License Plate Detection and Recognition in Unconstrained Scenarios [pdf] github 的项目地址: ...

  5. switch-case的选择用法

    企业发放的奖金根据利润提成.利润I低于或等于100000元的,奖金可提0.1:利润高于100000元,低于200000(100000<I<=200000)时,低于100000元的部分按10 ...

  6. vue项目与node项目分离

    为了前后端分离,我们在前端和api层中间,架构了一层node层,用来做服务端渲染,来加快用户的首屏可用和对搜索引擎的友好.项目一开始放置在同一个git仓库里面,分别放在client目录和server目 ...

  7. mysql——单表查询——聚合函数——概念

    使用聚合函数查询 group by关键字通常和聚合函数一起使用 .count()函数 count()函数用来统计记录的条数 举例:使用count()函数统计employee表的记录数 select c ...

  8. CSS:文字水平居中的写法

    <view class='kk'> 水平垂直居中文字 </view> .kk{ border: 1px solid #000000; width: 200px; height: ...

  9. Linux用ICMP协议实现简单Ping网络监测功能

    ICMP是(Internet Control Message Protocol)Internet控制报文协议.它是TCP/IP协议族的一个子协议,用于在IP主机.路由器之间传递控制消息.控制消息是指网 ...

  10. 最新的省市编码和sql

    下面的项目是整理的最新的省市编码sql文件,可以看看. github