zoukankan      html  css  js  c++  java
  • 为什么需要override和new两种多态方案

    有时候我们纠结迷茫,不知所措,其实最大的症结就在于不愿意深想,不愿意假设,不愿意宏观地去看待一切。虽然我们可能永远都无法从微软那里获得DotNet的源代码来研读,无法了解DotNet的内部机制,但并不代表我们不能自己对DotNet的内部机制进行假设和推断。要成为一个名优秀的软件工程师,学会使用推理的手法解决是一个非常有趣的过程,我们可以象江户川柯南一样在实际发生的事件中去推理真理。

    什么是推理?推理就是由一个或几个已知的判断(前提),推导出一个未知的结论的思维过程。推理是一种形式逻辑。是研究事物形式及其规律和一些简单的逻辑方法的科学。其作用是从已知的知识得到未知的知识,特别是可以得到不可能通过感觉经验掌握的未知知识。

    中国有一句老话:知其然,须求其所以然。在本章我们会使用一些常用的推理手法通过C#的一些在语法的定义和编译器的运行一系列的推理式学习,通过这个方式,读者不但可以更透彻的了解C#知识,更能学习到一种新型的学习模式,在将来遇到各种困惑又没有足够可以查阅资料的情况下,能得到正确的结果。

    18.1 对概念的推理方法:为什么有哪些关键字?

    18.1.1 溯因推理:为什么需要override和new两种多态方案

    溯因推理是指根据事物发展过程所造成的结果。推断形成结果的一系列原因的整个逻辑思维过程,通俗的说就是从已知结果推断其原因的一种思维方式,其目的是得到一个最佳的解释,是一种典型的由果及因的方式。在逻辑结构上,它包括以下要素:

    n 观察现象陈述

    n 导致观察现象的可能原因即猜测性假说

    在C#的面向对象中,override是使用比较多的关键字。但你是否知道为什么需要这两个关键字,并且当子类类型转为基类类型是,这两个关键字定义的成员将有什么不同吗?

    在推理override和new的机制之前,我们再次回顾下这两个关键字的含义和用途。

    n override:要扩展或修改继承的方法、属性、索引器或事件的抽象实现或虚实现,必须使用 override 修饰符(来自MSDN)。

    n new:在用作修饰符时,new 关键字可以显式隐藏从基类继承的成员。隐藏继承的成员时,该成员的派生版本将替换基类版本(来自MSDN)。

    在中文的术语中我们将override称为重写而new称为覆盖,并且MSDN在描述“何时使用 Override 和 New 关键字”时语焉不详,仅给出了以下的使用建议和原则:

    在 C# 中,派生类可以包含与基类方法同名的方法,其定义的原则是:默认情况下,C# 方法为非虚方法。如果某个方法被声明为虚方法,则继承该方法的任何类都可以实现它自己的版本。若要使方法成为虚方法,必须在基类的方法声明中使用 virtual 修饰符。然后,派生类可以使用 override 关键字重写基虚方法,或使用 new 关键字隐藏基类中的虚方法。如果 override 关键字和 new 关键字均未指定,编译器将发出警告,并且派生类中的方法将隐藏基类中的方法。

    为了解释这段另人头晕的文字,微软在MSDN中还给出了一个更令人膛目结舌的案例。不过为了让读者能快速清晰的了解这段文字想描述的含义,我将其总结为三条口诀:

    n 任何非抽象(abstract)的实例方法在C#中默认是非虚拟(virtual)的

    n 子类要定义和父类中非抽象(abstract)或非虚拟(virtual)的同名方法时,必须使用new(覆盖)关键字

    n 子类要定义和父类中抽象或虚拟的同名方法时,必须使用override(重写)关键字

    以下我们将实现一个继承的模型,这个模型中我将使用到override和new关键字,类的对象图如下

    图Order类对象模型

    以下我们将通过代码实现这个模型,请读者仔细观察和跟随实现。

    我们首先定义一个Order类,该类只有两个方法,其中PrintInfo方法是虚拟的,其目的是输出一个简单的信息;PrintAuthor方法是非虚拟的,其目的是输出当前Windows操作系统登录人员的名字。

    public class Order

    {

    public virtual void PrintInfo()

    {

    System.Console.WriteLine("Order...");

    }

    public void PrintAuhor()

    {

    System.Console.WriteLine(System.Environment.UserName);

    }

    }

    然后我们从Order类继承一个派生类ShipOrder,该类使用override关键字重写了PrintInfo方法

    public class ShipOrder : Order

    {

    public override void PrintInfo()

    {

    System.Console.WriteLine("ShipOrder");

    }

    }

    我们再次从Order类继承一个派生类RoadOrder,该类使用override关键字重写了PrintInfo方法,并且使用new关键字覆盖了Order中非虚拟的PrintAuhor方法

    public class RoadOrder : Order

    {

    public override void PrintInfo()

    {

    System.Console.WriteLine("RoadOrder");

    }

    public new void PrintAuhor()

    {

    System.Console.WriteLine(System.Environment.UserDomainName);

    }

    }

    为展示这些类运行的结果,我们编写以下的代码

    class Program

    {

    static void Main(string[] args)

    {

    new Order().PrintInfo();

    new Order().PrintAuhor();

    new ShipOrder().PrintInfo();

    new ShipOrder().PrintAuhor();

    new RoadOrder().PrintInfo();

    new RoadOrder().PrintAuhor();

    }

    }

    在查看显示结果前,笔者建议你自己先想像下运行的结果应该怎么样。下图是运行的结果

    如果结果和你想像的一样,那么恭喜你,你对这两个关键字的基本用法已经非常明确了。但是现在我们想像下,如果将子类ShipOrder和RoadOrder强制的转为Order类型,并且再次调用PrintInfo和PrintAuthor方法会有什么情况出现呢?换句话说:将子类转换为父类的类型调用时,override和new方法是实现父类的行文还是自己自身的行为呢?

    以下的运行代码演示如何进行这个测试

    class Program

    {

    static void Main(string[] args)

    {

    new Order().PrintInfo();

    new Order().PrintAuhor();

    System.Console.WriteLine("----------------");

    ((Order)new ShipOrder()).PrintInfo();

    ((Order)new ShipOrder()).PrintAuhor();

    System.Console.WriteLine("----------------");

    ((Order)new RoadOrder()).PrintInfo();

    ((Order)new RoadOrder()).PrintAuhor();

    }

    }

    运行的结果如图,不知道和你想像的结果是否一致?

    我们总结下运行的结果

    当子类转换为父类,且以父类的形式进行方法调用时:override总是指向自己的实现,而new总是指向父类的实现。

    那么为什么会出现这样的情况呢?请查看下目前我们所了解的事实:

    当父类的方法定义为抽象(abstract)或虚拟(virtual)时,子类使用override关键字

    当父类没有将方法定义为抽象(abstract)或虚拟(virtual)时,子类使用new关键字

    现在我们将这个事实用表格的方式进行统计,统计的结果为:

    父类方法的修饰

    Abstract

    virtual

    默认

    子类调用父类方法

    失败

    成功

    成功

    子类调用自己的方法

    成功

    成功

    成功

    子类同名方法定义关键字

    override

    override

    new

    从上面的表格我们可以清晰的得到如下的判断

    n 因为override时候父类的成员有可能是abstract,所以调用其父类方法有50%的失败可能。

    n 使用new关键字的时候,父类的成员必须是已经实现的,所以调用其父类决不可能会失败。

    因此,当子类类型转换为基类类型时,为了确保最大的安全系数,使用override修饰的成员,都将指向子类自己的成员实现,而当成员是new定义的话,可以放心的调用父类类型中定义的成员了。

  • 相关阅读:
    mogodb 设置用户名密码认证
    axon mogoconfig
    ListUtils 对 list数据 分组 ,统计,求和 。。。
    jQuery 之 dom操作
    学习Java第二天
    字节跳动spring面试题,你能回答出几个
    CH340芯片选型
    Django的路由转换器的使用
    Vue之cli脚手架
    String中split(regex,limit)方法讲解
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/1744109.html
Copyright © 2011-2022 走看看