深入浅出OOP(三): 多态和继承(动态绑定/运行时多态)

在前面的文章中,我们介绍了编译期多态、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

 
     
        Main(= ==

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关键字。

 
     
            
     
           
     
        Main(= = =

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

3个类的运行时多态

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

 
     
            
     
            
     
         
     
        Main(= = =

运行结果:

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。

切断关系

           
     
        Main(= =

执行结果如下:

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

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

4个类的运行时多态

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

 
     
        
     
        
     
         
     
        
     
        Main(= = ==

执行结果如下:

ClassB XXX

ClassB XXX

ClassD XXX

ClassD XXX

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

永无止境的循环

 
     
        
     
        
     
        Main(=

运行报错:

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)

时间: 2024-10-10 02:58:19

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

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

MSDN定义:枚举类型(也称为枚举)为定义一组可以赋给变量的命名整数常量提供了一种有效的方法.  例如,假设您必须定义一个变量,该变量的值表示一周中的一天. 该变量只能存储七个有意义的值. 若要定义这些值,可以使用枚举类型.枚举类型是使用 enum 关键字声明的. 从OOP上来说,枚举的角色和和class一样,它创建了一种新的数据类型. 1: namespace Enums 2: { 3: class Program 4: { 5: static void Main(string[] args)

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

访问修饰符(或者叫访问控制符)是面向对象语言的特性之一,用于对类.类成员函数.类成员变量进行访问控制.同时,访问控制符也是语法保留关键字,用于封装组件. Public, Private, Protected at Class Level 在创建类时,我们需要考虑类的作用域范围,如谁可访问该类,谁可访问该类成员变量,谁可访问该类成员函数. 换而言之,我们需要约束类成员的访问范围.一个简单的规则,类成员函数.类成员变量之间可以自由 访问不受约束,这里主要说的是外部的访问约束.在创建class的时候,

深入浅出OOP(五): C#访问修饰符(Public/Private/Protected/Inter

深入浅出OOP(五): C#访问修饰符(Public/Private/Protected/Internal/Sealed/Constants) 访问修饰符(或者叫访问控制符)是面向对象语言的特性之一,用于对类.类成员函数.类成员变量进行访问控制.同时,访问控制符也是语法保留关键字,用于封装组件. Public, Private, Protected at Class Level 在创建类时,我们需要考虑类的作用域范围,如谁可访问该类,谁可访问该类成员变量,谁可访问该类成员函数. 换而言之,我们需

深入浅出OOP(四): 多态和继承(抽象类)

在本文中,我们讨论OOP中的热点之一:抽象类.抽象类在各个编程语言中概念是一致的,但是C#稍微有些不一样.本文中我们会通过代码来实现抽象类,并一一进行解析. 深入理解OOP(一):多态和继承(初期绑定和编译时多态) 深入理解OOP(二):多态和继承(继承) 深入理解OOP(三):多态和继承(动态绑定和运行时多态) 深入理解OOP(四):多态和继承(C#中的抽象类) 深入理解OOP(五):C#中的访问修饰符(Public/Private/Protected/Internal/Sealed/Cons

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

本文是深入浅出OOP第二篇,主要说说继承的话题. 深入理解OOP(一):多态和继承(初期绑定和编译时多态) 深入理解OOP(二):多态和继承(继承) 深入理解OOP(三):多态和继承(动态绑定和运行时多态) 深入理解OOP(四):多态和继承(C#中的抽象类) 深入理解OOP(五):C#中的访问修饰符(Public/Private/Protected/Internal/Sealed/Constants/Static and Readonly Fields) 深入理解OOP(六):枚举(实用方法)

继承、封装、多态

学习完类与对象终于认识到什么是类,什么是对象了.接下来要看的就是java的三大特征:继承.封装.多态. 一.封装(数据的隐藏) 在定义一个对象的特性的时候,有必要决定这些特性的可见性,即哪些特性对外部是可见的,哪些特性用于表示内部状态.通常,应禁止直接访问一个对象中数据的实际表示,而应通过操作接口来访问,这称为信息隐藏. 1.1.封装的步骤    1).使用private 修饰需要封装的成员变量.        2.)提供一个公开的方法设置或者访问私有的属性             设置 通过s

继承、final、多态、内部类、异常

继承(上): 1. 提高了代码的重用性 2. 让类与类之间产生关系,有了这个关系,才有多态性 注意:千万不要为了获取其他功能,简化代码而继承: 必须是类与类之间有所属关系才可以继承,所属关系 is a. 在java语言中只能是单继承,不支持多继承. 因为:多继承容易带来安全隐患,当多个父类有相同的功能, 当功能内容不同时,子对象不确定运行哪一个. 但是java保留这种机制,用另一种形式表示:多实现. 并且存在多层继承,也就是一个继承体系. 如何使用一个继承体系中的功能? 要想使用体系,先查阅体系

浅谈C++非多态单继承数据布局

最近在看<深度探索C++对象模型>,打算先总结下C++中的数据布局,这篇暂时先谈谈非多态(non-polymorphic)单继承的情况: 一般而言,当我们谈及C++中的继承和多态就默认进入到其面向对象的语境中了.封装是基础,公有继承(public)是手段,然后带来运行时多态(run-time polymorphism)的弹性,通过“基类指针或引用可以透明的指向任何派生类对象”这句话来支撑着C++的面向对象体系.目的无他,无非是简化用户(这里的用户指的是类的使用者)的使用逻辑,即归一化.归一化说

【Java基本功】一文了解Java中继承、封装、多态的细节

本节主要介绍Java面向对象三大特性:继承 封装 多态,以及其中的原理. 本文会结合虚拟机对引用和对象的不同处理来介绍三大特性的原理. 继承 Java中的继承只能单继承,但是可以通过内部类继承其他类来实现多继承. public class Son extends Father{public void go () {System.out.println("son go");}public void eat () {System.out.println("son eat"