【转】多态与 new [C#]

本文探讨了C#中多态的使用方式及new关键字的作用,分析了new关键字如何影响多态性,并通过代码示例展示了如何正确处理多态性和new关键字的关系。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

0. 浪子 《今天你多态了吗?》 提出两个这样的 问题

1) “使用基类继承多态,有一点特别需要注意的就是:基类(抽象或者非抽象)中需要获得多态效果的成员必须有 abstractvirtual 修饰。”使用 new 来重写的成员不能形成多态吗? 2) “多态就是使得你能够用一种统一的方式来处理一组各具个性却同属一族的不同个体的机制。” new 重写后的成员是否符合了这个范畴?

 

1. 你通常怎样用多态?

        假设我有一个类,里面有一个 PrintStatus 方法,用于打印实例的当前状态,我希望该类的派生类都带有一个 PrintStatus 方法,并且这些方法都用于打印其实例的当前状态。那么我会这样表达我的愿望:

None.gif//  Code #01
None.gif

None.gif
class  Base
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif    
public   virtual   void  PrintStatus()
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif {
InBlock.gif        Console.WriteLine(
" public virtual void PrintStatus() in Base " );
ExpandedSubBlockEnd.gif    }

ExpandedBlockEnd.gif}

于是我可以写一个这样的方法:

None.gif//  Code #02
None.gif

None.gif
public   void  DisplayStatusOf(Base[] bs)
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif    
foreach  (Base b  in  bs)
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif {
InBlock.gif        b.PrintStatus();
ExpandedSubBlockEnd.gif    }

ExpandedBlockEnd.gif}

bs 中可能包含着不同的 Base 的派生类,但我们却可以忽略这些“个性”而使用一种统一的方式来处理某事。在 .NET 2.0 中,XmlReader 的 Create 有这样一个版本:

None.gifpublic   static  XmlReader Create(Stream input);

你 可以向 Create 传递任何可用的“流”,例如来自文件的“流”(FileStream)、来自内存的“流”(MemoryStream)或来自网络的“流 ”(NetworkStream)等。虽然每一中“流”的工作细节都不同,但我们却使用一种统一的方式来处理这些“流”。

 

2. 假如有人不遵守承诺...

        DisplayStatusOf 隐含着这样一个假设:bs 中如果存在派生类的实例,那么该派生类应该重写 PrintStatus,当然必须加上 override 关键字:

None.gif//  Code #03
None.gif

None.gif
class  Derived1 : Base
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif    
public   override   void  PrintStatus()
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif {
InBlock.gif        Console.WriteLine(
" public override void PrintStatus() in Derived1 " );
ExpandedSubBlockEnd.gif    }

ExpandedBlockEnd.gif}
None.gif

你可以把这看作一种承诺、约定,直到有人沉不住气...

None.gif//  Code #04
None.gif

None.gif
class  Derived2 : Base
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif    
public   new   void  PrintStatus()
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif {
InBlock.gif        Console.WriteLine(
" public new void PrintStatus() in Derived2 " );
ExpandedSubBlockEnd.gif    }

ExpandedBlockEnd.gif}

假设我们有这样一个数组:

None.gif//  Code #05
None.gif

None.gifBase[] bs 
=   new  Base[]
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif    
new  Base(),
InBlock.gif    
new  Derived1(),
InBlock.gif    
new  Derived2()
ExpandedBlockEnd.gif}
;

把它传递给 DisplayStatusOf,则输出是:

None.gif//  Output #01
None.gif
None.gif
//  public virtual void PrintStatus() in Base
None.gif
//  public override void PrintStatus() in Derived1
None.gif
//  public virtual void PrintStatus() in Base

从输出结果中很容易看出 Derived2 并没有按照我们期望的去做。但你无需惊讶,这是由于 Derived2 的设计者没有“遵守约定”的缘故。

 

3. new:封印咒术

        new 似乎给人一种这样的感觉,它的使用者喜欢打破别人的约定,然而,如果使用恰当,new 可以弥补基类设计者的“短见”。在 Creating a Data Bound ListView Control 中,Rockford Lhotka 就示范了如何封印原来的 ListView.Columns,并使自行添加的返回 DataColumnHeaderCollection 的 Columns 取而代之。

        从 Output #01 中我们可以看到,new 只是把 Base.PrintStatus 封印起来而不是消灭掉,你可以解除封印然后进行访问。对于 Derived2 的使用者,解封的方法是把 Derived2 的实例转换成 Base 类型:

None.gif//  Code #06
None.gif

None.gifBase d2 
=   new  Derived2();
None.gifd2.PrintStatus();
None.gif
None.gif
//  Output #02
None.gif
None.gif
//  public virtual void PrintStatus() in Base

而在 Derived2 内部,你可以透过 base 来访问:

None.gif//  Code #07
None.gif

None.gif
base .PrintStatus();

这种方法是针对实例成员的,如果被封印的成员是静态成员的话,就要透过类名来访问了。

 

4. 假如 Base.PrintStatus 是某个接口的隐式实现...

        假如 Base 实现了一个 IFace 接口:

None.gif//  Code #08
None.gif

None.gif
interface  IFace
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif    
void  PrintStatus();
ExpandedBlockEnd.gif}

None.gif
None.gif
class  Base : IFace
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif    
public   virtual   void  PrintStatus()
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif {
InBlock.gif        Console.WriteLine(
" public virtual void PrintStatus() in Base " );
ExpandedSubBlockEnd.gif    }

ExpandedBlockEnd.gif}

我们只需要让 Derived2 重新实现 IFace:

None.gif//  Code #09
None.gif

None.gif
class  Derived2 : Base, IFace
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif    
public   new   void  PrintStatus()
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif {
InBlock.gif        Console.WriteLine(
" public new void PrintStatus() in Derived2 " );
ExpandedSubBlockEnd.gif    }

ExpandedBlockEnd.gif}

Derived1 保持不变。则把:

None.gif//  Code #10
None.gif

None.gifIFace[] fs 
=   new  IFace[]
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif    
new  Base(),
InBlock.gif    
new  Derived1(),
InBlock.gif    
new  Derived2(),
ExpandedBlockEnd.gif}

传递给:

None.gif//  Code #11
None.gif

None.gif
public   void  DisplayStatusOf(IFace[] fs)
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif    
foreach  (IFace f  in  fs)
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif {
InBlock.gif        f.PrintStatus();
ExpandedSubBlockEnd.gif    }

ExpandedBlockEnd.gif}

的输出结果是:

None.gif//  Output #03
None.gif
None.gif
//  public virtual void PrintStatus() in Base
None.gif
//  public override void PrintStatus() in Derived1
None.gif
//  public new void PrintStatus() in Derived2

       从输出结果中,我们可以看到,虽然 Derived2.PrintStatus 应用了 new,但却依然参与动态绑定,这是由于 new 只能割断 Derived2.PrintStatus 和 Base.PrintStatus 的联系,而不能割断它与 IFace.PrintStatus 的联系。我在 Derived2 的定义中重新指定实现 IFace,这将使得编译器认为 Derived2.PrintStatus 是 IFace.PrintStatus 的隐式实现,于是,在动态绑定时 Derived2.PrintStatus 就被包括进来了。

 

5. 谁的问题?

        我必须指出,如果 Base(Code #01)和 Derived2(Code #04)同时存在的话,它们俩其中一个存在着设计上的问题。为什么这样说呢?Base 的设计者在 PrintStatus 上应用 virtual 说明了他希望派生类能透过重写这一方法来参与动态绑定,即多态性;而 Derived2 的设计者在 PrintStatus 上应用 new 则说明了他希望割断 Derived2.PrintStatus 和 Base.PrintStatus 之间的联系,这将使得 Derived2.PrintStatus 无法参与到 Base 的设计者所期望的动态绑定中。如果在 Base.PrintStatus 上应用 virtual(即对多态性的期望)是合理的话,那么 Derived2.PrintStatus 应该换用另外一个名字了;如果在 Derived2.PrintStatus 上应用 new(即否决参与动态绑定)是合理的,那么 Base.PrintStatus 应该考虑是否去掉 virtual 了,否则就会出现一些奇怪的行为,例如 Output #01 的第三行输出。

        假如继承体系中多态性行为的期望是合理的话,那么更实际的做法应该是把 Base 定义成这样:

None.gif//  Code #12
None.gif

None.gif
abstract   class  Base
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif    
public   abstract   void  PrintStatus();
ExpandedBlockEnd.gif}

而原来 Base 中的实现应该下移到一个派生类中:

None.gif//  Code #13
None.gif

None.gif
class  Derived3 : Base
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif    
public   override   void  PrintStatus()
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif {
InBlock.gif        Console.WriteLine(
" public override void PrintStatus() in Derived3 [originally implemented in Base] " );
ExpandedSubBlockEnd.gif    }

ExpandedBlockEnd.gif}

这样,Derived2.PrintStatus 将使得编译无法完成,从而迫使其设计者要么更改方法的名字,要么换用 override 修饰。这种强制使得 Derived2 的设计者不得不重新考虑其设计的合理性。

        假如继承体系中多态性行为的期望不总是合理呢?例如 Stream 有这样一个方法:

None.gifpublic   abstract   long  Seek( long  offset, SeekOrigin origin);

现在假设我有一个方法在处理输入流时需要用到 Stream.Seek:

None.gif//  Code #14
None.gif

None.gif
public   void  Resume(Stream input,  long  offset)
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif    
//  dot.gif
InBlock.gif

InBlock.gif    input.Seek(offset, SeekOrigin.Begin);
InBlock.gif
InBlock.gif    
//  dot.gif
ExpandedBlockEnd.gif
}

当我们向 Resume 传递一个 NetworkStream 的实例,Resume 将会抛出一个 NotSupportedException,因为 NetworkStream 不支持 Seek。那么这是否说明 Stream 的设计有问题呢?

设想 Resume 是一个下载工具进行断点续传的方法,然而,并不是所有的服务器都支持断点续传的,于是,你需要首先判断输入流是否支持 Seek 操作,再决定如何处理输入流:

None.gif//  Code #15
None.gif

None.gif
public   void  Resume(Stream input,  long  offset)
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif    
if  (input.CanSeek)
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif {
InBlock.gif        
//  dot.gif
InBlock.gif

InBlock.gif        input.Seek(offset, SeekOrigin.Begin);
InBlock.gif
InBlock.gif        
//  dot.gif
ExpandedSubBlockEnd.gif
    }

InBlock.gif    
else
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif {
InBlock.gif        
//  dot.gif
ExpandedSubBlockEnd.gif
    }

ExpandedBlockEnd.gif}

如果 CanSeek 为 false,那就只好从头来过了。

        实际上,我们并不能保证任何 Stream 的派生类都能够支持某个(些)操作,我们甚至不能保证来自同一个派生类的所有实例都支持某个(些)操作。你可以设想有这样一个 PriorityStream,它能够根据当前登录账号的权限来决定是否提供写操作,这使得拥有足够权限的人才能修改数据。或许 Stream 的设计者已经预料到这类情况的发生,所以 CanRead、CanSeek 和 CanWrite 就被加入到 Stream 里了。

        值得注意的是,Code #07 的 Derived2 可能是一个很糟糕的设计,也可能是一个很实用的设计。在本文,它是一个很糟糕的设计,如果你足够细心,你会察觉到 Derived2 的设计者希望 Derived2.PrintStatus 绕过 Base.PrintStatus 而直接和 IFace.PrintStauts 进行关联,表面上这没什么不妥,但实质上 Base.PrintStatus 和 IFace.PrintStauts 在约定上是同质的,这意味着如果与 IFace.PrintStauts 进行关联就等于承认自己和 Base.PrintStatus 是同质的,这样的话,为什么不直接在 Derived2 里重写 PrintStatus 呢?在《基类与接口混合继承的声明问题》中,我示范了一个实用的设计,用 new 和接口重新实现(Interface reimplementation)来纠正非预期的多态行为。

 

6. 最后...

        当我的朋友拿着问题来找我时,我通常都不会直接给出我的答案,而是尽我的能力向他提供足够多的可用信息,以便他能够根据他所面临的实际情况作出处理,毕 竟,我不会比他更了解他的问题,而他也应该形成他自己的关于他的问题的思考。我希望浪子能用自己的答案回答他所提出的问题,因为只有这样,那些知识才真正 属于他,并且我也相信本文已经提供了足够多的可用信息。

原文URL:http://www.cnblogs.com/allenlooplee/archive/2006/03/13/348760.html

转载于:https://www.cnblogs.com/jeriffe/articles/1421098.html

内容概要:本文档提供了关于“微型车间生产线的设计生产数据采集试验研究”的毕业设计复现代码,涵盖从论文结构生成、机械结构设计、PLC控制系统设计、生产数据采集分析系统、有限元分析、进度管理、文献管理论文排版系统的完整实现。通过Python代码API调用,详细展示了各个模块的功能实现相互协作。例如,利用SolidWorks API设计机械结构,通过PLC控制系统模拟生产流程,使用数据分析工具进行生产数据的采集异常检测,以及利用进度管理系统规划项目时间表。 适合人群:具有机械工程、自动化控制或计算机编程基础的学生或研究人员,尤其是从事智能制造领域相关工作的人员。 使用场景及目标:①帮助学生或研究人员快速搭建理解微型车间生产线的设计实现;②提供完整的代码框架,便于修改扩展以适应不同的应用场景;③作为教学或科研项目的参考资料,用于学习研究智能制造技术。 阅读建议:此资源不仅包含详细的代码实现,还涉及多个学科领域的知识,如机械设计、电气控制、数据分析等。因此,在学习过程中,建议读者结合实际操作,逐步理解每个模块的功能原理,并尝试调整参数以观察不同设置下的系统表现。同时,可以参考提供的文献资料,深入研究相关理论技术背景。
本次的学生体质健康信息管理网站,按照用户的角色可以分为教师学生,后台设置管理员角色来对学生的信息进行管理。,设计如下: 1、后台管理系统 后台管理系统主要是为该系统的管理员提供信息管理服务的系统,具体包括的功能模块如下: (1)管理员信息管理 (2)教师信息管理 (3)学生信息管理 (4)健康信息统计(图形化进行健康,亚健康等学生的信息数量统计) 2、教师角色的功能模块设计 教师角色所需要的功能模块主要包括了如下的一些内容: (1)个人资料修改 (2)学生体质健康管理:录入相关数据,包括但不限于身高、体重、肺活量、视力等生理指标以及运动能力、身体成分、骨密度等健康指标,并且设置健康,亚健康状态 (3)学生健康建议:根据体质信息,进行学生健康的建议 (4)健康预警:对健康出问题的学生,进行健康预警 (5)饮食锻炼情况管理,查看 3、学生角色 学生角色可以通过该信息网站看到个人的基本信息,能够看到教师给学生的健康建议等,功能模块设计如下: (1)个人资料修改 (2)我的健康建议查看 (3)我的健康预警 (4)饮食锻炼情况管理,记录平时的饮食锻炼情况 完整前后端源码,部署后可正常运行! 环境说明 开发语言:Java后端 框架:ssm,mybatis JDK版本:JDK1.8+ 数据库:mysql 5.7+ 数据库工具:Navicat11+ 开发软件:eclipse/idea Maven包:Maven3.3+ 部署容器:tomcat7.5+
网站前台: (1)站内新闻:及时发布康复中心动态、行业资讯等,让用户了解最新消息。 (2)用户注册,登录:支持用户注册新账号并登录系统,开启预约等操作。 (3)科室介绍:详细介绍康复中心各科室,含功能、特色治疗等信息。 (4)医生列表,详情:展示医生信息,如履历、擅长领域,助用户选医生。 (5)老年生活风采:呈现老年人康复生活照片等,展示康复后的精彩状态。 (6)预约入院:用户填写姓名、电话等信息,提交入院预约申请。 网站后台: 管理员 (1)管理员密码修改:管理员可自主修改登录密码,保障账号安全。 (2)用户注册管理,审核:对新用户注册信息审核,确保信息真实合规。 (3)站内新闻管理:发布、编辑、删除站内新闻,把控资讯更新质量。 (4)科室信息管理:维护科室信息,包括介绍、设备等内容的增删改。 (5)医生信息管理:管理医生资料,可更新履历、擅长方向等信息。 (6)老年生活风采管理:上传、整理、替换老年生活风采相关展示内容。 (7)预约入院管理:处理用户入院预约,安排入院时间流程。 用户 (1)用户资料修改:用户可修改个人注册资料,保证信息准确性。 (2)我的预约住院结果:查询预约入院审核结果,了解住院安排情况。 完整前后端源码,部署后可正常运行! 环境说明 开发语言:Java后端 框架:ssm,mybatis JDK版本:JDK1.8+ 数据库:mysql 5.7+ 数据库工具:Navicat11+ 开发软件:eclipse/idea Maven包:Maven3.3+ 部署容器:tomcat7.5+
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值