C# 多态性

news2024/11/16 21:27:18

简单来讲,多态,就是派生类的对象可以隐式转化为基类对象。在派生类中可以重写基类中定义并实现的虚方法。

可以用基类声明,用派生类实例化,这样的变量调用方法时会调用运行时方法(即派生类重写的方法)。

想在派生类中调用基类的方法,可以用  base.基类方法();  的形式。具体例子见下文。

C继承B继承A(的虚方法),则不论C强制转化成B还是A,都会调用重写链条中最后的一次重写。

    public class A
    {
        public virtual string Output()
        {
            return "A方法";
        }
    }

    public class B:A
    {
        public override string Output()
        {
            return "B方法";
        }
    }

    public class C:B
    {
        public override string Output()
        {
            return "C方法";
        }
    }

测试代码:

        private void button9_Click(object sender, EventArgs e)
        {
                C Cclass = new C();
                button9.Text = Cclass.Output() + Environment.NewLine;
                B Bclass = (B)Cclass;
                button9.Text += Bclass.Output() + Environment.NewLine;
                A Aclass = (A)Cclass;
                button9.Text += Aclass.Output() + Environment.NewLine;

                A a = new A();
                button9.Text += a.Output() + Environment.NewLine;
        }

结果:

以下是官方文档的说明:

 官方文档:多形性 - C# | Microsoft Learn

Polymorphism(多态性)是一个希腊词,指“多种形态”,多态性具有两个截然不同的方面:

  • 在运行时,在方法参数和集合或数组等位置,派生类的对象可以作为基类的对象处理。 在出现此多形性时,该对象的声明类型不再与运行时类型相同。
  • 基类可以定义并实现虚方法,派生类可以重写这些方法,即派生类提供自己的定义和实现。 在运行时,客户端代码调用该方法,CLR 查找对象的运行时类型,并调用虚方法的重写方法。 你可以在源代码中调用基类的方法,执行该方法的派生类版本。

虚方法允许你以统一方式处理多组相关的对象。 例如,假定你有一个绘图应用程序,允许用户在绘图图面上创建各种形状。 你在编译时不知道用户将创建哪些特定类型的形状。 但应用程序必须跟踪创建的所有类型的形状,并且必须更新这些形状以响应用户鼠标操作。 你可以使用多态性通过两个基本步骤解决这一问题:

  1. 创建一个类层次结构,其中每个特定形状类均派生自一个公共基类。
  2. 使用虚方法通过对基类方法的单个调用来调用任何派生类上的相应方法。

首先,创建一个名为 RectangleShape 的基类,并创建一些派生类,例如 TriangleCircle、 和 。 为 Shape 类提供一个名为 Draw 的虚拟方法,并在每个派生类中重写该方法以绘制该类表示的特定形状。 创建 List<Shape> 对象,并向其添加 CircleTriangle 和 Rectangle

public class Shape
{
    // A few example members
    public int X { get; private set; }
    public int Y { get; private set; }
    public int Height { get; set; }
    public int Width { get; set; }

    // Virtual method
    public virtual void Draw()
    {
        Console.WriteLine("Performing base class drawing tasks");
    }
}

public class Circle : Shape
{
    public override void Draw()
    {
        // Code to draw a circle...
        Console.WriteLine("Drawing a circle");
        base.Draw();
    }
}
public class Rectangle : Shape
{
    public override void Draw()
    {
        // Code to draw a rectangle...
        Console.WriteLine("Drawing a rectangle");
        base.Draw();
    }
}
public class Triangle : Shape
{
    public override void Draw()
    {
        // Code to draw a triangle...
        Console.WriteLine("Drawing a triangle");
        base.Draw();
    }
}

若要更新绘图图面,请使用 foreach 循环对该列表进行循环访问,并对其中的每个 Shape 对象调用 Draw 方法。 虽然列表中的每个对象都具有声明类型 Shape,但调用的将是运行时类型(该方法在每个派生类中的重写版本)。

// Polymorphism at work #1: a Rectangle, Triangle and Circle
// can all be used wherever a Shape is expected. No cast is
// required because an implicit conversion exists from a derived
// class to its base class.
var shapes = new List<Shape>
{
    new Rectangle(),
    new Triangle(),
    new Circle()
};

// Polymorphism at work #2: the virtual method Draw is
// invoked on each of the derived classes, not the base class.
foreach (var shape in shapes)
{
    shape.Draw();
}
/* Output:
    Drawing a rectangle
    Performing base class drawing tasks
    Drawing a triangle
    Performing base class drawing tasks
    Drawing a circle
    Performing base class drawing tasks
*/

在 C# 中,每个类型都是多态的,因为包括用户定义类型在内的所有类型都继承自 Object。

多形性概述

虚拟成员

当派生类从基类继承时,它包括基类的所有成员。 基类中声明的所有行为都是派生类的一部分。 这使派生类的对象能够被视为基类的对象。 访问修饰符(publicprotectedprivate 等)确定是否可以从派生类实现访问这些成员。 通过虚拟方法,设计器可以选择不同的派生类行为:

  • 派生类可以重写基类中的虚拟成员,并定义新行为。
  • 派生类可能会继承最接近的基类方法而不重写方法,同时保留现有的行为,但允许进一步派生的类重写方法。
  • 派生类可以定义隐藏基类实现的成员的新非虚实现。

仅当基类成员声明为 virtual 或 abstract 时,派生类才能重写基类成员。 派生成员必须使用 override 关键字显式指示该方法将参与虚调用。 以下代码提供了一个示例:

public class BaseClass
{
    public virtual void DoWork() { }
    public virtual int WorkProperty
    {
        get { return 0; }
    }
}
public class DerivedClass : BaseClass
{
    public override void DoWork() { }
    public override int WorkProperty
    {
        get { return 0; }
    }
}

字段不能是虚拟的,只有方法、属性、事件和索引器才可以是虚拟的。 当派生类重写某个虚拟成员时,即使该派生类的实例被当作基类的实例访问,也会调用该成员。 以下代码提供了一个示例:

DerivedClass B = new DerivedClass();
B.DoWork();  // Calls the new method.

BaseClass A = B;
A.DoWork();  // Also calls the new method.

虚方法和属性允许派生类扩展基类,而无需使用方法的基类实现。 有关详细信息,请参阅使用 Override 和 New 关键字进行版本控制。 接口提供另一种方式来定义将实现留给派生类的方法或方法集。

使用新成员隐藏基类成员

如果希望派生类具有与基类中的成员同名的成员,则可以使用 new 关键字隐藏基类成员。 new 关键字放置在要替换的类成员的返回类型之前。 以下代码提供了一个示例:

public class BaseClass
{
    public void DoWork() { WorkField++; }
    public int WorkField;
    public int WorkProperty
    {
        get { return 0; }
    }
}

public class DerivedClass : BaseClass
{
    public new void DoWork() { WorkField++; }
    public new int WorkField;
    public new int WorkProperty
    {
        get { return 0; }
    }
}

通过将派生类的实例强制转换为基类的实例,可以从客户端代码访问隐藏的基类成员。 例如:

DerivedClass B = new DerivedClass();
B.DoWork();  // Calls the new method.

BaseClass A = (BaseClass)B;
A.DoWork();  // Calls the old method.

阻止派生类重写虚拟成员

无论在虚拟成员和最初声明虚拟成员的类之间已声明了多少个类,虚拟成员都是虚拟的。 如果类 A 声明了一个虚拟成员,类 B 从 A 派生,类 C 从类 B 派生,则不管类 B 是否为虚拟成员声明了重写,类 C 都会继承该虚拟成员,并可以重写它。 以下代码提供了一个示例:

public class A
{
    public virtual void DoWork() { }
}
public class B : A
{
    public override void DoWork() { }
}

派生类可以通过将重写声明为 sealed 来停止虚拟继承。 停止继承需要在类成员声明中的 override 关键字前面放置 sealed 关键字。 以下代码提供了一个示例:

public class C : B
{
    public sealed override void DoWork() { }
}

在上一个示例中,方法 DoWork 对从 C 派生的任何类都不再是虚拟方法。 即使它们转换为类型 B 或类型 A,它对于 C 的实例仍然是虚拟的。 通过使用 new 关键字,密封的方法可以由派生类替换,如下面的示例所示:

public class D : C
{
    public new void DoWork() { }
}

在此情况下,如果在 D 中使用类型为 D 的变量调用 DoWork,被调用的将是新的 DoWork。 如果使用类型为 CB 或 A 的变量访问 D 的实例,对 DoWork 的调用将遵循虚拟继承的规则,即把这些调用传送到类 C 的 DoWork 实现。

从派生类访问基类虚拟成员

已替换或重写某个方法或属性的派生类仍然可以使用 base 关键字访问基类的该方法或属性。 以下代码提供了一个示例:

public class Base
{
    public virtual void DoWork() {/*...*/ }
}
public class Derived : Base
{
    public override void DoWork()
    {
        //Perform Derived's work here
        //...
        // Call DoWork on base class
        base.DoWork();
    }
}

有关详细信息,请参阅 base。

 备注

建议虚拟成员在它们自己的实现中使用 base 来调用该成员的基类实现。 允许基类行为发生使得派生类能够集中精力实现特定于派生类的行为。 未调用基类实现时,由派生类负责使它们的行为与基类的行为兼容。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1047609.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于Vue和Element UI实现前后端分离和交互

目录 前言 一、Element UI简介 1.Element UI是什么 2.Element UI的特点 二、项目搭建 1.创建一个SPA项目 2.安装 Element-UI 3.导入组件 4.创建登陆注册界面 登录组件---Login.vue 注册组件---Register.vue 定义组件与路由的对应关系 效果演示&#xff1a; 三、前…

激活函数总结(四十六):激活函数补充(Nipuna、StarReLU)

激活函数总结&#xff08;四十六&#xff09;&#xff1a;激活函数补充 1 引言2 激活函数2.1 Nipuna激活函数2.2 StarReLU激活函数 3. 总结 1 引言 在前面的文章中已经介绍了介绍了一系列激活函数 (Sigmoid、Tanh、ReLU、Leaky ReLU、PReLU、Swish、ELU、SELU、GELU、Softmax、…

Ros2 学习01-Ros2 VS Ros1

ROS最早的设计目标就是开发这样一款PR2家庭服务机器人&#xff0c;这款机器人绝大部分时间都是独立工作&#xff0c;为了让他具备充足的能力&#xff1a; 它搭载了工作站级别的计算平台和各种先进的通信设备&#xff0c;不用担忧算力不够&#xff0c;有足够的实力支持各种复杂…

【VUE复习·7】样式绑定:静态样式绑定、动态样式绑定(明亮模式 / 暗黑模式 切换的效果如何实现)

总览 1.静态样式绑定 2.动态样式绑定 一、静态样式绑定 1.正常写即可 <div><div class"basic" click"changeName">{{name}}</div> </div><style>.basic{...} </style>二、动态样式绑定 1.示例 这么写&#xff0…

K8s Kubelet 垃圾回收机制

前言 Kubelet 垃圾回收(Garbage Collection)是一个非常有用的功能,它负责自动清理节点上的无用镜像和容器。Kubelet 每隔 1 分钟进行一次容器清理,每隔 5 分钟进行一次镜像清理(截止到 v1.15 版本,垃圾回收间隔时间还都是在源码中固化的,不可自定义配置)。如果节点上已…

2023经典好用的图床网站推荐(站长必备)

图床一般是指储存图片的服务器&#xff0c;有国内和国外之分&#xff0c;国外的图床由于有空间距离等因素决定访问速度很慢影响图片显示速度。国内也分为单线空间、多线空间和cdn加速三种。同时允许你把图片对外连接的网上空间&#xff0c;图床有免费的&#xff0c;也有收费的。…

成都瀚网科技:抖音上线地方方言自动翻译功能

为了让很多方言的地域历史、文化、习俗能够以短视频的形式生产、传播和保存&#xff0c;解决方言难以被更多用户阅读和理解的问题&#xff0c;平台正式上线推出当地方言自动翻译功能。创作者可以利用该功能&#xff0c;将多个方言视频“一键”转换为普通话字幕供大众观看。 具体…

NOSQL Redis Ubuntu系列 常用的配置 及密码登录

查看Ubuntu 版本 uname -a 配置redis.conf 查看redis 是否安装成功 ps -ef | grep redis 查看redis 服务状态 service redis status 查看redis 默认安装的路径 whereis redis #sudo vim /etc/redis.conf redis 密码登录

程序员发展应该尽早明白13个道理(计算机专业的一定要看!)

1、一定要确定自己的发展方向&#xff0c;并为此目的制定可行的计划。不要说什么&#xff0c;“我刚毕业&#xff0c;还不知道将来可能做什么&#xff1f;”&#xff0c;“跟着感觉走&#xff0c;先做做看”。因为&#xff0c;这样的观点会通过你的潜意识去暗示你的行为无所事事…

Qt扫盲-QSqlField 理论总结

QSqlField 理论总结 一、QSqlField 类概述二、QSqlField 使用三、QSqlRecord类概述四、QSqlRecord的使用 一、QSqlField 类概述 QSqlField 表示数据库表 或 视图中单个列的特征&#xff0c;说白了就是数据库记录里的数据字段。如数据类型和列名。字段还包含数据库列的值&#…

vue.draggable拖拽,项目中三个表格互相拖拽的实例操作,前端分页等更多小技巧~

vue.draggable中文文档 - itxst.com官网在这里&#xff0c;感兴趣的小伙伴可以看看。 NPM或yarn安装方式 yarn add vuedraggable npm i -S vuedraggable UMD浏览器直接引用JS方式 <script src"https://www.itxst.com/package/vue/vue.min.js"></script&…

什么是超声波清洗机?工作原理是什么?2023年超声波清洗机推荐

超声波清洗机的优点可真是太多了&#xff01;&#xff01;&#xff01;比如超声波清洗的效果很不错&#xff0c;清洁度也很高&#xff0c;清洗速度快&#xff0c;不需要用手去接触清洗液&#xff0c;对于深孔&#xff0c;细缝&#xff0c;工件暗区也能清洗干净&#xff0c;清洗…

企业怎样选择适合的服务器租用?

随着互联网技术的发展&#xff0c;如何选择企业需要的服务器租用来满足需求是很多企业目前在考虑的问题&#xff0c;今天就让小编来给大家讲一讲吧&#xff01; 确定好服务器的规模和用途。企业首先根据自身的业务情况选择服务器的数量和规模还有性能&#xff0c;小型企业可以…

Echarts 自适应不生效解决(CPK分析工具直方图为例)

示例代码为左上方的CPK分析直方图组件 <template><div ref="cpk" id="cpk" style="height: 300px; width: 100%"></div> </template><script> import * as echarts from "echarts"; import { deboun…

【多线程初阶】多线程案例之定时器

文章目录 前言1. 什么是定时器2. 标准库中的定时器3. 自己实现一个定时器总结 前言 本文主要给大家讲解多线程的一个重要案例 — 定时器. 关注收藏, 开始学习吧&#x1f9d0; 1. 什么是定时器 定时器也是软件开发中的一个重要组件 类似于一个 “闹钟”. 达到一个设定的时间之…

Figma中文插件,让设计工作事半功倍的6大神器

Figma 凭借强大的设计功能和出色的协同体验&#xff0c;成为当前最受欢迎的 UI 设计工具之一。其插件生态为设计师提供了更多实用功能和可能性&#xff0c;大幅提高工作效率。即时设计在原型、交互、设计、协作等方面与 Figma 旗鼓相当&#xff0c;但更考虑本土设计师的实际需求…

数字孪生与GIS:优化公共交通的未来

数字孪生结合地理信息系统&#xff08;GIS&#xff09;在公共交通领域具有潜在的重大贡献&#xff0c;这种结合可以帮助城市更高效地规划、运营和改进公共交通系统。以下是一些关键方面的讨论&#xff0c;以说明数字孪生和GIS在这一领域的作用&#xff1a; 数字孪生技术的兴起…

【计算机网络】 基于TCP的简单通讯(服务端)

文章目录 流程伪代码代码实现加载库创建套接字绑定ip地址和端口号监听接受连接收发数据关闭套接字、卸载库 流程伪代码 //1、加载库——WSAStartup()//2、创建套接字——socket()//3、绑定ip和端口号——bind()//4、监听——listen()while(true){//5、接受连接——accept()whi…

kkplayer用户手册

本软件不使用任何敏感权限都可拒绝。所有资源均来自互联网&#xff0c;本软件仅供学习参考使用。 有任何问题可先尝试重装最新版 1.青少年模式 2.搜索方法 3.单个添加视频源 4.批量添加视频源 5.无法播放,无法全屏 6.DLNA投屏 7.隐私权限问题 8.数据备份和分享 9.关于广告 1. …

线程池解析

文章目录 1、平时使用哪些线程池&#xff0c;线程池默认的参数有哪些2、线程池的7个参数3、线程池状态①、线程池各个状态切换图&#xff1a; 4、线程池的使用5、线程池的好处6、线程池的整个流程7、Java的线程池说说①、线程池概念②、线程池的创建③、任务执行④、四种拒绝策…