在前面的文章中,我们介绍了编译期多态、params关键字、实例化、base关键字等。本节我们来关注另外一种多态:运行时多态, 运行时多态也叫迟绑定。

运行时多态或迟绑定、动态绑定

在C#语音中,运行时多态也叫方法重写(overriding),我们可以在子类中overriding基类的同签名函数,使用“virtual & override”关键字即可。

C#的New、Override关键字

创建一个console 示例工程,命名为InheritanceAndPolymorphism。在Program.cs基础上,再添加2个类文件,分别命名为ClassA.cs、ClassB.cs。拷贝如下代码:

public class ClassA
{
public void AAA()
{
Console.WriteLine("ClassA AAA");
} public void BBB()
{
Console.WriteLine("ClassA BBB");
} public void CCC()
{
Console.WriteLine("ClassA CCC");
}
}

ClassB:

public class ClassB
{
public void AAA()
{
Console.WriteLine("ClassB AAA");
} public void BBB()
{
Console.WriteLine("ClassB BBB");
} public void CCC()
{
Console.WriteLine("ClassB CCC");
}
}

在上面的代码中,我们可以看到ClassA、ClassB有同样签名的方法,可以在program.cs中直接使用。

我们对代码再做休整,结构如下:

/// <summary>
/// ClassB, acting as a base class
/// </summary>
public class ClassB
{
public void AAA()
{
Console.WriteLine("ClassB AAA");
} public void BBB()
{
Console.WriteLine("ClassB BBB");
} public void CCC()
{
Console.WriteLine("ClassB CCC");
}
} /// <summary>
/// Class A, acting as a derived class
/// </summary>
public class ClassA : ClassB
{
public void AAA()
{
Console.WriteLine("ClassA AAA");
} public void BBB()
{
Console.WriteLine("ClassA BBB");
} public void CCC()
{
Console.WriteLine("ClassA CCC");
}
}

Program.cs

    /// <summary>
/// Program: used to execute the method.
/// Contains Main method.
/// </summary>
public class Program
{
private static void Main(string[] args)
{
ClassA x = new ClassA();
ClassB y=new ClassB();
ClassB z=new ClassA(); x.AAA(); x.BBB(); x.CCC();
y.AAA(); y.BBB();y.CCC();
z.AAA(); z.BBB(); z.CCC();
}
}

F5,运行代码,结果如下:

ClassA AAA

ClassA BBB

ClassA CCC

ClassB AAA

ClassB BBB

ClassB CCC

ClassB AAA

ClassB BBB

ClassB CCC

但同时,在VS的Output窗口,我们获得了3个Warnings:

'InheritanceAndPolymorphism.ClassA.AAA()' hides inherited member

'InheritanceAndPolymorphism.ClassB.AAA()'. Use the new keyword if hiding was intended.

'InheritanceAndPolymorphism.ClassA.BBB()' hides inherited member

'InheritanceAndPolymorphism.ClassB.BBB()'. Use the new keyword if hiding was intended.

'InheritanceAndPolymorphism.ClassA.CCC()' hides inherited member

'InheritanceAndPolymorphism.ClassB.CCC()'. Use the new keyword if hiding was intended.

这些Warnings的原因是因为子类和基类的AAA、BBB、CCC方法签名相同,尽管从执行上看优先执行子类同签名的方法,但是可能会有潜在的问题,故Warnings提出。

重构实验

基于上面的Warning,我们手动修改代码,看看如何消除这些Warnings。

先给子类添加new、override关键字试试:

/// <summary>
/// Class A, acting as a derived class
/// </summary>
public class ClassA : ClassB
{
public override void AAA()
{
Console.WriteLine("ClassA AAA");
} public new void BBB()
{
Console.WriteLine("ClassA BBB");
} public void CCC()
{
Console.WriteLine("ClassA CCC");
}
}

执行的结果是报错了:

Error: 'InheritanceAndPolymorphism.ClassA.AAA()': cannot override inherited member 'InheritanceAndPolymorphism.ClassB.AAA()' because it is not marked virtual, abstract, or override

从这个错误提示信息看,我们需要修改基类方法,如添加virtual关键字。

/// <summary>
/// ClassB, acting as a base class
/// </summary>
public class ClassB
{
public virtual void AAA()
{
Console.WriteLine("ClassB AAA");
} public virtual void BBB()
{
Console.WriteLine("ClassB BBB");
} public virtual void CCC()
{
Console.WriteLine("ClassB CCC");
}
} /// <summary>
/// Class A, acting as a derived class
/// </summary>
public class ClassA : ClassB
{
public override void AAA()
{
Console.WriteLine("ClassA AAA");
} public new void BBB()
{
Console.WriteLine("ClassA BBB");
} public void CCC()
{
Console.WriteLine("ClassA CCC");
}
} /// <summary>
/// Program: used to execute the method.
/// Contains Main method.
/// </summary>
public class Program
{
private static void Main(string[] args)
{
ClassB y = new ClassB();
ClassA x = new ClassA();
ClassB z = new ClassA(); y.AAA(); y.BBB(); y.CCC();
x.AAA(); x.BBB(); x.CCC();
z.AAA(); z.BBB(); z.CCC(); Console.ReadKey();
}
}

执行,则无Warning了,通过这个实例,我们得知通过在基类添加Virtual关键字授权其子类可override基类同签名方法的权限,方便了OOP的扩展。

3个类的运行时多态

ClassA\ClassB基础上,下面添加ClassC,看看3个类继承关系的运行时多态:

/// <summary>
/// ClassB, acting as a base class
/// </summary>
public class ClassB
{
public void AAA()
{
Console.WriteLine("ClassB AAA");
} public virtual void BBB()
{
Console.WriteLine("ClassB BBB");
} public virtual void CCC()
{
Console.WriteLine("ClassB CCC");
}
} /// <summary>
/// Class A, acting as a derived class
/// </summary>
public class ClassA : ClassB
{
public virtual void AAA()
{
Console.WriteLine("ClassA AAA");
} public new void BBB()
{
Console.WriteLine("ClassA BBB");
} public override void CCC()
{
Console.WriteLine("ClassA CCC");
}
} /// <summary>
/// Class C, acting as a derived class
/// </summary>
public class ClassC : ClassA
{
public override void AAA()
{
Console.WriteLine("ClassC AAA");
} public void CCC()
{
Console.WriteLine("ClassC CCC");
}
} /// <summary>
/// Program: used to execute the method.
/// Contains Main method.
/// </summary>
public class Program
{
private static void Main(string[] args)
{
ClassB y = new ClassA();
ClassB x = new ClassC();
ClassA z = new ClassC(); y.AAA(); y.BBB(); y.CCC();
x.AAA(); x.BBB(); x.CCC();
z.AAA(); z.BBB(); z.CCC(); Console.ReadKey();
}
}

运行结果:

ClassB AAA

ClassB BBB

ClassA CCC

ClassB AAA

ClassB BBB

ClassA CCC

ClassC AAA

ClassA BBB

ClassA CCC

如果基类声明了virtual 关键字,子类可使用override修饰符实现运行时多态:只有在编译器动态决定是否被调用。

如果未标明virtual或非virtual,则方法是否被调用在编译期就能决定。

再看看下面的例子:

internal class A
{
public virtual void X()
{
}
} internal class B : A
{
public new void X()
{
}
} internal class C : B
{
public override void X()
{
}
}

F5运行,结果报错了:

Error: 'InheritanceAndPolymorphism.C.X()': cannot override inherited member 'InheritanceAndPolymorphism.B.X()' because it is not marked virtual, abstract, or override

错误的原因是A中定义了virtual的X函数,在B中用new关键字隐藏了A中的X函数。当C尝试通过override关键字的时候,是获得不了A中的virtual关键字X函数的,既在C中X函数为非Virtual的,故不能override。

切断关系

internal class A
{
public virtual void X()
{
Console.WriteLine("Class: A ; Method X");
}
} internal class B : A
{
public new virtual void X()
{
Console.WriteLine("Class: B ; Method X");
}
} internal class C : B
{
public override void X()
{
Console.WriteLine("Class: C ; Method X");
}
} /// <summary>
/// Program: used to execute the method.
/// Contains Main method.
/// </summary>
public class Program
{
private static void Main(string[] args)
{
A a = new C();
a.X();
B b = new C();
b.X(); Console.ReadKey();
}
}

执行结果如下:

Class: A ; Method X
Class: C ; Method X

在这里,我们通过在B类中添加new Virtual修饰符,然后在C中即可使用B中Virtual的X函数了。

4个类的运行时多态

在上面继承上,在运行时多态中添加第四个类:ClassD。

/// <summary>
/// Class A
/// </summary>
public class ClassA
{
public virtual void XXX()
{
Console.WriteLine("ClassA XXX");
}
} /// <summary>
/// ClassB
/// </summary>
public class ClassB:ClassA
{
public override void XXX()
{
Console.WriteLine("ClassB XXX");
}
} /// <summary>
/// Class C
/// </summary>
public class ClassC : ClassB
{
public virtual new void XXX()
{
Console.WriteLine("ClassC XXX");
}
} /// <summary>
/// Class D
/// </summary>
public class ClassD : ClassC
{
public override void XXX()
{
Console.WriteLine("ClassD XXX");
}
} /// <summary>
/// Program: used to execute the method.
/// Contains Main method.
/// </summary>
public class Program
{
private static void Main(string[] args)
{
ClassA a = new ClassD();
ClassB b = new ClassD();
ClassC c=new ClassD();
ClassD d=new ClassD(); a.XXX();
b.XXX();
c.XXX();
d.XXX(); Console.ReadKey();
}
}

执行结果如下:

ClassB XXX

ClassB XXX

ClassD XXX

ClassD XXX

第一行输出中,来自a.XXX()函数 , 我们在 ClassA中定义了XXX函数,然后在ClassB中使用new关键字切断了virtual关系--对子类而言。因此XXX函数从ClassC开始成为新的virtual函数,在这个代码中a是ClassD的实例,但是声明的为ClassA,故从下往上找,找到ClassB的XXX函数,打印并输出结果。

永无止境的循环

/// <summary>
/// Class A
/// </summary>
public class ClassA
{
public virtual void XXX()
{
Console.WriteLine("ClassA XXX");
}
} /// <summary>
/// ClassB
/// </summary>
public class ClassB:ClassA
{
public override void XXX()
{
((ClassA)this).XXX();
Console.WriteLine("ClassB XXX");
}
} /// <summary>
/// Program: used to execute the method.
/// Contains Main method.
/// </summary>
public class Program
{
private static void Main(string[] args)
{
ClassA a = new ClassB();
a.XXX(); }
}

运行报错:

Error: {Cannot evaluate expression because the current thread is in a stack overflow state.}

在这个例子中,((ClassA)this).XXX(); 导致了循环调用,修改为base.XXX即可修复这个强转导致的循环调用。

结论

  • 在C#中,子类对象可赋值给一个基类对象;相反需要强转。
  • override关键字用于子类重写同签名的基类virtual函数
  • 用new和override可重写基类virtual的同签名函数
  • virtual修饰符的函数,只能在运行时决定是否被执行
  • 函数未用virtual修饰,则在编译期即可决定是否被调用

原文链接:Diving in OOP (Day 3): Polymorphism and Inheritance (Dynamic Binding/Run Time Polymorphism)

深入浅出OOP(三): 多态和继承(动态绑定/运行时多态)的更多相关文章

  1. 深入理解OOP(三):多态和继承(动态绑定和运行时多态)

    在前面的文章中,我们介绍了编译期多态.params关键字.实例化.base关键字等.本节我们来关注另外一种多态:运行时多态, 运行时多态也叫迟绑定. 深入理解OOP(一):多态和继承(初期绑定和编译时 ...

  2. C++中的动态类型与动态绑定、虚函数、运行时多态的实现

    动态类型与静态类型 静态类型 是指不需要考虑表达式的执行期语义,仅分析程序文本而决定的表达式类型.静态类型仅依赖于包含表达式的程序文本的形式,而在程序运行时不会改变.通俗的讲,就是上下文无关,在编译时 ...

  3. C++ //多态 //静态多态:函数重载 和 运算符重载 属于静态多态 ,复用函数名 //动态多态:派生类和虚函数实现运行时多态

    1 //多态 2 //静态多态:函数重载 和 运算符重载 属于静态多态 ,复用函数名 3 //动态多态:派生类和虚函数实现运行时多态 4 5 //静态多态和动态多态的区别 6 //静态多态的函数地址早 ...

  4. [翻译]Go与C#对比 第三篇:编译、运行时、类型系统、模块和其它的一切

    Go vs C#, Part 3: Compiler, Runtime, Type System, Modules, and Everything Else | by Alex Yakunin | S ...

  5. 字节码编程,Javassist篇三《使用Javassist在运行时重新加载类「替换原方法输出不一样的结果」》

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 通过前面两篇 javassist 的基本内容,大体介绍了:类池(ClassPool) ...

  6. 深入浅出OOP(六): 理解C#的Enums

    MSDN定义:枚举类型(也称为枚举)为定义一组可以赋给变量的命名整数常量提供了一种有效的方法.  例如,假设您必须定义一个变量,该变量的值表示一周中的一天. 该变量只能存储七个有意义的值. 若要定义这 ...

  7. 深入浅出OOP(五): C#访问修饰符(Public/Private/Protected/Internal/Sealed/Constants)

    访问修饰符(或者叫访问控制符)是面向对象语言的特性之一,用于对类.类成员函数.类成员变量进行访问控制.同时,访问控制符也是语法保留关键字,用于封装组件. Public, Private, Protec ...

  8. 深入理解OOP(二):多态和继承(继承)

    本文是深入浅出OOP第二篇,主要说说继承的话题. 深入理解OOP(一):多态和继承(初期绑定和编译时多态) 深入理解OOP(二):多态和继承(继承) 深入理解OOP(三):多态和继承(动态绑定和运行时 ...

  9. 深入理解OOP(第一天):多态和继承(初期绑定和编译时多态)

    在本系列中,我们以CodeProject上比较火的OOP系列博客为主,进行OOP深入浅出展现. 无论作为软件设计的高手.或者菜鸟,对于架构设计而言,均需要多次重构.取舍,以有利于整个软件项目的健康构建 ...

随机推荐

  1. oracle SQL查询中间若干条记录

    方法一:利用rownum和差集函数minus select * from ( select * from emp order by sal) where rownum<13 minus sele ...

  2. scala学习:apply方法

    摘抄两段话: 在明确了方法调用的接收者的情况下,若方法只有一个参数时,调用的时候就可以省略点及括号.如 " to ",实际完整调用是 ".to()".但 &qu ...

  3. 【python3】collections系列介绍

    文章来源:http://www.jb51.net/article/48771.htm (http://www.cnblogs.com/wushank/p/5122786.html) 修改人:天马流行拳 ...

  4. 使用rownum对oracle分页

    以Student表为例进行分页 建表及插入 -- 有表结构如下 create table STUDENT ( sno INTEGER, sname ), sage INTEGER ); -- 插入数据 ...

  5. js灵活打印web页面区域内容的通用方法

      我们做网站,经常需要打印页面指定区域的内容,而网上关于这块的说法很多,各种各样的打印控件也不少.但许多打印方案都不怎么好,至少我不喜欢,要么封装复杂,要么难以维护.正好现在的项目也需要用到 ...

  6. 問題排查:在 ServiceModel 客戶端配置部份中,找不到名稱 和協定 的終結點元素。

    同樣都是刪掉服務參考再重建重編譯重發行,為什麼之前幾次都沒事? 這次只不過是刪掉服務參考,然後換了個名稱重建而已,做完就變這樣? 後來發現問題出在 app.config,因為之前 app.config ...

  7. IOS网络请求原理

    1,为什么要用到网络,  只有通过网络跟外界进行数据交互,数据更新,应用才能保持哦新鲜.活力 如果没有网络,也就缺少数据变化.变成一滩死水. 2, 良好的移动网络应用 = 良好的UI + 良好的用户体 ...

  8. Odoo10尝鲜:出勤登记

    非常的cool,使用一个 联网的PAD 就能使用超级潮的 出勤登记功能     扫描 工牌条码,或者 从员工目录选择 员工, 例如 输入 PIN 密码, 防止"代打卡"       ...

  9. 安装数据库出现错误vc_red.msi找不到

    用虚拟光驱安装数据的时候可能会出现,找不到vc_red.msi的问题,通过加载的虚拟光驱目录设置,可能 仍然后问题,比如程序停止运行. 解决方法是:解压iso文件,用解压后的文件安装.然后在解压文件夹 ...

  10. three.js 之旅 (五)--跟场景scene相关的函数

    1.scene.add(obj);   在场景中添加物体 2.scene.remove(obj);   在场景中移除物体 3.scene.children();  获取场景中所有子对象的列表 4.sc ...