C# virtual 是虚拟的含义,在 C# 语言中,默认情况下类中的成员都是非虚拟的,通常将类中的成员定义成虚拟的,表示这些成员将会在继承后重写其中的内容。

virtual 关键字能修饰方法、属性、索引器以及事件等,用到父类的成员中。

使用 virtual 关键字修饰属性和方法的语法形式如下。

//修饰属性
public  virtual  数据类型  属性名{get; set; }

//修饰方法
访问修饰符  virtual  返回值类型方法名
{
    语句块;
}

需要注意的是,virtual 关键字不能修饰使用 static 修饰的成员。

此外,virtual 关键字既可以添加到访问修饰符的后面,也可以添加到访问修饰符的前面,但实际应用中习惯将该关键字放到访问修饰符的后面。

子类继承父类后能重写父类中的成员,重写的关键字是 override。

所谓重写是指子类和父类的成员定义一致,仅在子类中增加了 override 关键字修饰成员。

例如在父类中有一个求长方形面积的方法,方法定义如下。

  1. publie int Area(int x, int y)
  2. {
  3. return x * y
  4. }

在子类中重写该方法的代码如下。

public override int Area(int x,int y)
{
    语句块;
    return  整数类型的值;
}

在子类中重写父类中的方法后能改变方法体中的内容,但是方法的定义不能改变。

【实例 1】将上一节《C# base》中定义的 Person 类中的 Print 方法更改为虚拟的方法,分别用 Student 类和 Teacher 类继承 Person 类,并重写 Print 方法,打印出学生信息和教师信息。

为了减少重复的代码,在每个类中省略了属性部分的定义内容,仅保留 Print 方法部分的内容,实现的代码如下。

  1. class Person
  2. {
  3. public virtual void Print()
  4. {
  5. Console.WriteLine("编号:"+ Id);
  6. Console.WriteLine("姓名:"+ Name);
  7. Console.WriteLine("性别:"+ Sex);
  8. Console.WriteLine("身份证号:"+ Cardid);
  9. Console.WriteLine("联系方式:"+ Tel);
  10. }
  11. }
  12. class Student:Person
  13. {
  14. public override void Print()
  15. {
  16. Console.WriteLine("编号:"+ Id);
  17. Console.WriteLine("姓名:"+ Name);
  18. Console.WriteLine("性别:"+ Sex);
  19. Console.WriteLine("身份证号:"+ Cardid);
  20. Console.WriteLine("联系方式:"+ Tel);
  21. Console.WriteLine("专业:"+ Major);
  22. Console.WriteLine("年级:"+ Grade);
  23. }
  24. }
  25. class Teacher:Person
  26. {
  27. public override void Print()
  28. {
  29. Console.WriteLine("编号:"+ Id);
  30. Console.WriteLine("姓名:"+ Name);
  31. Console.WriteLine("性别:"+ Sex);
  32. Console.WriteLine("身份证号:"+ Cardid);
  33. Console.WriteLine("联系方式:"+ Tel);
  34. Console.WriteLine("专业:"+ Major);
  35. Console.WriteLine("年级:"+ Grade);
  36. }
  37. }

通过上面的代码即可完成对 Person 类中 Print 方法的重写,在重写后的 Print 方法中能直接调用在 Person 类中定义的成员。

但读者会发现在 Person 类的 Print 中已经对 Person 中的相关属性编写了输出操作的代码,而每一个子类中又重复地编写了代码,造成代码的冗余,也没有体现出代码重用的特点。

如果能在重写父类方法的同时直接使用父类中已经编写过的内容就会方便很多。

在重写 Print 方法后仍然需要使用 base 关键字调用父类中的 Print 方法执行相应的操作。

【实例 2】改写实例 1 中的 Student 和 Teacher 类中重写的 Print 方法,使用 base 关键字调用父类中的 Print 方法。

根据题目要求,更改后的代码如下。

  1. class Student:Person
  2. {
  3. public override void Print()
  4. {
  5. base.Print ();
  6. Console.WriteLine("专业:"+ Major);
  7. Console.WriteLine("年级:"+ Grade);
  8. }
  9. }
  10. class Teacher:Person
  11. {
  12. public override void Print()
  13. {
  14. base.Print ();
  15. Console.WriteLine("专业:"+ Major);
  16. Console.WriteLine("年级:"+ Grade);
  17. }
  18. }

从上面的代码可以看出继承给程序带来的好处,不仅减少了代码的冗余,还增强了程序的可读性。

方法隐藏和重写方法有区别吗?这是很多初学者常问的问题。观察以下代码,思考结果会是什么?

  1. class Program
  2. {
  3. static void Main(string[] args)
  4. {
  5. A a1 = new B();
  6. a1.Print();
  7. A a2 = new C();
  8. a2.Print();
  9. }
  10. }
  11. class A
  12. {
  13. public virtual void Print()
  14. {
  15. Console.WriteLine("A");
  16. }
  17. }
  18. class B :A
  19. {
  20. public new void Print()
  21. {
  22. Console.WriteLine("B");
  23. }
  24. }
  25. class C :A
  26. {
  27. public override void Print()
  28. {
  29. Console.WriteLine("C");
  30. }
  31. }

执行上面的代码,效果如下图所示。

从上面的执行效果可以看出,使用方法隐藏的方法调用的结果是父类 A 中 Print 方法中的内容,而使用方法重写的方法调用的结果是子类 C 中 Print 方法中的内容。

因此方法隐藏相当于在子类中定义新方法,而方法重写则是重新定义父类中方法的内容。

从上面的代码也可以看出,在“A a1=new B()”语句中 A 类是父类、B 类是子类,相当于将子类转换成父类,即隐式转换。

如果需要将父类转换成子类,则需要强制转换,并且在强制转换前需要先将所需的子类转换成父类,示例代码如下。

  1. A a2=new C();
  2. C c=(C) a2;
  3. c.Print();

在上面的实例中,a2 是父类对象,然后将其强制转换成 C 类对象。

Object 类中的 ToString 方法能被类重写,并返回所需的字符串,通常将其用到类中返回类中属性的值。

在 Student 类中添加重写的 ToString 方法,代码如下。

  1. class Student
  2. {
  3. public string Major{ get; set;}
  4. public string Grade{ get; set;}
  5. public void Print()
  6. {
  7. Console.WriteLine("专业:"+ Major);
  8. Console.WriteLine("年级:"+ Grade);
  9. }
  10. public override string ToString()
  11. {
  12. return Major+","+Grade;
  13. }
  14. }

这样,在调用 Student 类中的 ToString 方法时即可获取专业和年级的值。

此外,除了 ToString 方法,在类中也可以重写 Equals 方法、GetHashCode 方法。

C# virtual 函数的更多相关文章

  1. 09——绝不在构造和析构函数中调用virtual函数

    在base class构造期间,virtual函数不是virtual函数. 构造函数.析构函数中不要调用virtual函数.

  2. 考虑virtual函数以外的选择

    在C++中,有四种选择可以替代virtual函数的功能: 1.non-virtual interface(NVI)手法,这是一种template method模式.它以public non-virtu ...

  3. Effective C++ -----条款35:考虑virtual函数以外的其他选择

    virtual函数的替代方案包括NVI手法及Strategy设计模式的多种手法.NVI手法自身是一个特殊形式的Template Method设计模式. 将机能从成员函数移到class外部函数,带来的一 ...

  4. Effective C++ -----条款09:绝不在构造和析构过程中调用virtual函数

    在构造和析构期间不要调用virtual函数,因为这类调用从不下降至derived class(比起当前执行构造函数和析构函数的那层).

  5. 为什么内联函数,构造函数,静态成员函数不能为virtual函数

    http://blog.csdn.net/freeboy1015/article/details/7635012 为什么内联函数,构造函数,静态成员函数不能为virtual函数? 1> 内联函数 ...

  6. 条款9:不要在构造和析构过程中调用virtual函数

    如下是一个股票交易的例子: class Transaction // 交易的基类 { public: Transaction(); ; // 用于记录交易日志 }; Transaction::Tran ...

  7. effective c++:virtual函数的替代方案

    绝不重新定义继承来的缺省值 首先明确下,虚函数是动态绑定,缺省参数值是静态绑定 // a class for geometric shapes class Shape { public: enum S ...

  8. effective c++:virtual函数在构造函数和析构函数中的注意事项

    如不使用自动生成函数要明确拒绝 对于一个类,如果你没有声明,c++会自动生成一个构造函数,一个析构函数,一个copy构造函数和一个copy assignment操作符. class Empty { p ...

  9. 考虑virtual函数以外的其它选择

    详情见<Effective C++>item35 1.使用non-virtual interface(NVI)手法,这是Template Method设计模式的一种特殊形式. 它以publ ...

  10. 还原virtual函数的本质-----C++

    当你每次看到C++类中声明一个virtual函数,特别是看到了一个virtual的虚构函数.你知道它的意思吗?你肯定会毫不犹豫的回答:不就是多态么...在运行时确定具体的行为么...完全正确,但这里我 ...

随机推荐

  1. jQuery---弹幕效果

    弹幕效果 <!doctype html> <html> <head> <meta charset="utf-8"> <titl ...

  2. mysql批量插入数据uuid去重

    update base_problem set uuid=replace(uuid(),'-',''); update base_problem set uuid = UUID() where uui ...

  3. C#继承是个啥

    继承: 字面意思就是继承 如地主老王有500亩地,老王的儿子小王可以种这五百亩地可以随便拿这五百亩地上面的任何东西 如Controller 你要用从一个controller调用另一个controlle ...

  4. [HNOI2017] 大佬 - DP,BFS,Hash,单调性

    这真的是一道综合题.然而感觉A得莫名其妙,交上去的时候其实非常虚,然后就莫名其妙地AC了? 首先我们考虑到,所有和怼有关的操作都是时刻无关的.也就是说,我们把这些操作拆散放到任何时候都对结果不会有影响 ...

  5. C# SDO_GEOMETRY

    OracleParameter endGeometry = cmd.CreateParameter(); endGeometry.OracleDbType = OracleDbType.Object; ...

  6. JUC-分支合并框架

    一.原理 Fork:把一个复杂任务进行分拆,大事化小 Join:把分拆任务的结果进行合并 ForkJoinPool 分支合并池    类比=>   线程池 ForkJoinTask ForkJo ...

  7. python之路正则表达式

    元字符 蓝色框中有非贪婪模式也不起作用,非贪婪模式在+后加上?号 有空字符串

  8. AM335X的应用程序自启动流程以及U盘更新应用程序记录

    在AM335X的SD卡更新系统学习记录中最后更新完系统后,以后运行应用程序都会从EMMC中取出Linux系统运行.接着介绍Linux系统是怎么自己启动我们编写的应用程序的. 1.在AM335X的SD卡 ...

  9. adb logcat日志抓取

    adb命令 logcat日志抓取 一.logcat抓log方法:adb logcat命令,可以加条件过滤 1.安装SDK(参考android sdk环境安装) 2.使用数据线链接手机,在手机助手的sd ...

  10. c数据结构 -- 链表的理解

    链表是结构体变量与结构体变量链接在一起,怎么链接在一起?通过指针 #include <stdio.h> struct Node{ int data; struct Node* next; ...