我们先看下面一段程序:

  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. Java中的23种设计模式与7大原则

    一.创建型模式 1.抽象工厂模式(Abstract factory pattern): 提供一个接口, 用于创建相关或依赖对象的家族, 而不需要指定具体类.2.生成器模式(Builder patter ...

  2. IDEA配置JVM参数

  3. Android 夜间模式的实现

    package com.loaderman.daynightdemo; import android.os.Bundle; import android.support.v7.app.AppCompa ...

  4. flutter block回调

    block回调在oc中很常见,到了flutter中仍然有block回调 自定义一个StatefulWidget PageTitle 无参数回调VoidCallback VoidCallback onT ...

  5. java:Topic About(猴子吃桃,快速排序,选择排序,插入排序)

    1. 猴子吃桃问题:猴子第一天摘下若干个桃子,当即吃了一半,还不瘾,又多吃了一个 第二天早上又将剩下的桃子吃掉一半,又多吃了一个.以后每天早上都吃了前一天剩下的一半零一个.到第10天早上想再吃时,见只 ...

  6. 通过NGINX location实现一个域名访问多个项目

    location ~ \.php$ { root /home/webroot;    //此目录下有多个项目 project1 ,project2... fastcgi_pass $php_upstr ...

  7. beego项目部署到nginx(含http转https)

    beego项目部署到nginx(含http转https)    之前的程序部署到服务器采用的直接部署,比较方便,现在把它部署到nginx,以便后续的反向代理和负载均衡,同时,因为要接入微信小程序,所以 ...

  8. cocos2dx[3.2](11) 新事件分发机制

    在2.x中处理事件需要用到委托代理(delegate),相信学过2.x的触摸事件的同学,都知道创建和移除的流程十分繁琐. 而在3.x中由于加入了C++11的特性,而对事件的分发机制通过事件分发器Eve ...

  9. 2D轮播图

    目录结构 index.html <!DOCTYPE html><html lang="en"><head> <meta charset=& ...

  10. neo4j - 查询效率的几种优化思路

    最近在公司实习做的就是优化neo4j图形数据库查询效率的事,公司提供的是一个在Linux上搭建且拥有几亿个节点的数据库.开始一段时间主要是熟悉该数据库的一些基本操作,直到上周才正式开始步入了优化数据库 ...