• C#:类的继承--重写和多态


    在上一篇C#:类的继承的最后一部分,我简单演示了类的继承中,通过在子类中添加父类没有的成员实现了类成员的横向扩展。

    在本篇中,我们将演示如何对类成员进行纵向扩展,那就是通过重写来实现。

    重写是什么?

    • 重写是针对函数成员而言的;
    • 重写是子类通过修改继承自基类的函数成员而实现的一次版本更新;(版本更新--是为了方便理解而这样叫的)
    • 若要构成重写,基类的函数成员 需要被 virtual修饰;该函数成员在子类中需要被 overrride修饰。

    使用代码认识一下什么是重写:

    class Shape
    {
        public double Area { get; set; }
        public string Color { get; set; }
        //使用virtual表明 该函数成员的功能希望在子类中被更新
        public virtual void ShowInfo()
        {
            Console.WriteLine($"面积:{this.Area},颜色:{this.Color}");
        }
    }
    
    class Square : Shape
    {
        //通过override,告诉父类,我升级了ShowInfo的功能
        public override void ShowInfo()
        {
            Console.WriteLine("我有用四条边,且对边相等。。。。");
        }
    }
    

    在Program类的Main函数中测试

    class Program
    {
        static void Main(string[] args)
        {
            Square square = new Square();
            square.ShowInfo();
            Console.ReadLine();
        }
    }
    /*输出:我有用四条边,且对边相等。。。。*/
    

    下面这种形式算重写么?

    class Square : Shape
    {
        public void ShowInfo()
        {
            Console.WriteLine("我有用四条边,且对边相等。。。。");
        }
    }
    
    • 首先答案是:不算,因为子类的方法中没有被override修饰;
    • 那么这种形式算什么?我们看一下,编译器给我们的提示:

      这是编译器只是给我们的警告:在子类中写了一个和父类一样的方法,就会隐藏掉继承自父类的方法(我们还没有介绍多态,如果介绍了多态以后,你就能明白这样写是十分不推荐的。)
      警告中的后半段是说,我们可以使用override来重写这个方法;当然这个我们刚刚才认识过,这里就不采用这个建议。
      警告中的最后,告诉我们另一种合理的形式是,如果你真的是有意要隐藏掉父类的这个方法的话,你可以使用new关键修饰。
      好吧,那么我们使用一下new关键字吧:可以看到警告消失了
    • 当然最后,如果你没有理睬编译器的警告和提供的推荐,程序也能照常运行。编译器也拿你没办法咯。

    在引出多态的概念前,说一下 is a 的概念。

    • C#中有一个操作符叫 is,由它组成的表达式的计算结果表示前者和后者是否类型兼容;
    • 下面通过一个实例来使用一下 is 操作符:
    static void Main(string[] args)
    {
        Square square = new Square();
        var result = square is Object;//Object是所有类型的基类型,所以is表达式结果为true
        if (result)
        {
            Object o = (Object)square;//(Object)变灰,表明square可以隐式转换成Object类型引用
            Console.WriteLine(o.GetType().BaseType.FullName);
        }
        Console.ReadLine();
    }
    /*输出:ExtendsDemo.Shape*/
    

    从示例中,我们可以得到下面结论,如果两个类型之间存在继承关系,那么is表达式结果为true,则子类引用可以隐式转换成父类型引用

    什么是多态?

    • 父类型变量指向子类型对象;
    • 父类型中的函数成员被子类重写了;
    • 当父类型引用调用函数成员时,调用的时子类中重写了的函数成员;
    • 以上就是对多态的描述,它隐含了:继承、重写。

    1)最普通也是最常见的多态:

    namespace ExtendsDemo
    {
        class Program
        {
            static void Main(string[] args)
            {
                Square square = new Square();
                Shape shape = square;
                shape.ShowInfo();
                Console.ReadLine();
            }
        }
    
        class Shape
        {
            public double Area { get; set; }
            public string Color { get; set; }
            //使用virtual表明 该函数成员的功能希望在子类中被更新
            public virtual void ShowInfo()
            {
                Console.WriteLine($"面积:{this.Area},颜色:{this.Color}");
            }
        }
    
        class Square : Shape
        {
            public override void ShowInfo()
            {
                Console.WriteLine("我有用四条边,且对边相等。。。。");
            }
        }
    }
    /*输出结果:我有用四条边,且对边相等。。。。*/
    

    是否很有意思,一个Shape类型的变量,从我们第一直觉上判断,应该输出的是Shape类型的ShowInfo函数中的输出信息,但是结果却并非如此。这就是多态特殊之处。

    2) 一种破坏多态的形式

    class Square : Shape
    {
        public new void ShowInfo()
        {
            Console.WriteLine("我有用四条边,且对边相等。。。。");
        }
    }
    /*输出:面积:0,颜色:*/
    

    从输出结果可以看出,使用new修饰后的ShowInfo函数,不会被父类型引用调用到,而是调用了父类中原有的函数成员;本来我们将代码写成父类型变量=子类型对象的形式,就是为了使用多态,这样一来不就是破坏了多态性么?

    3) 通过多层继承链,理解"父类型引用永远调用的是继承链中最新版本的重写函数"这句话。

    namespace ExtendsDemo
    {
        class Program
        {
            static void Main(string[] args)
            {
                var zhengfangxing = new ZhengFangXing();
                Shape shape = zhengfangxing;
                shape.ShowInfo();
                Console.ReadLine();
            }
        }
    
        class Shape
        {
            public double Area { get; set; }
            public string Color { get; set; }
            //使用virtual表明 该函数成员的功能希望在子类中被更新
            public virtual void ShowInfo()
            {
                Console.WriteLine($"面积:{this.Area},颜色:{this.Color}");
            }
        }
    
        class Square : Shape
        {
            public override void ShowInfo()
            {
                Console.WriteLine("我有用四条边,且对边相等。。。。");
            }
        }
    
        class ZhengFangXing:Square
        {
            public override void ShowInfo()
            {
                Console.WriteLine("我是特殊的长方形。。。我叫正方形");
            }
        }
    }
    /*输出:我是特殊的长方形。。。我叫正方形*/
    

    从Shape到Squre再到ZhengFanxing总共经历了两次重写(我称它叫版本升级),那么Shape类型的变量访问的到继承链上的最新版本,就是ZhengFangXing的ShowInfo()

    4) 如果继承链某一层使用了new,你还能知道父类型引用调用哪个类的成员么?

    namespace ExtendsDemo
    {
        class Program
        {
            static void Main(string[] args)
            {
                var zhengfangxing = new ZhengFangXing();
                Shape shape = zhengfangxing;
                shape.ShowInfo();
                Console.ReadLine();
            }
        }
    
        class Shape
        {
            public double Area { get; set; }
            public string Color { get; set; }
            //使用virtual表明 该函数成员的功能希望在子类中被更新
            public virtual void ShowInfo()
            {
                Console.WriteLine($"面积:{this.Area},颜色:{this.Color}");
            }
        }
    
        class Square : Shape
        {
            public new virtual void ShowInfo()
            {
                Console.WriteLine("我有用四条边,且对边相等。。。。");
            }
        }
    
        class ZhengFangXing:Square
        {
            public override void ShowInfo()
            {
                Console.WriteLine("我是特殊的长方形。。。我叫正方形");
            }
        }
    }
    /*输出:面积:0,颜色:*/
    

    不知道你是否猜对?没关系,我们分析一下原因:在例子2中我们说过,new并不是一种重写形式,我更愿意把它当作是一种新成员(横向扩展),它不会带来版本更新;从基类出发顺着继承链,向下找到最新版本的重写;在new出现的那一层,基类发现则并不是一种重写(即没有最新版本),所以基类型引用就调用了自己的函数成员。

    父类型引用可以引用不同的子类实例,这是一种多态性的体现;父类型中的方法被子类重写,而拥有了各种各样的功能,这是多态的一种行为上的体现;多态性大大提升了程序的扩展性:

    namespace ExtendsDemo
    {
        class Program
        {
            static void Main(string[] args)
            {
                Shape square = new Square();
                Shape circle = new Circle();
                Shape trangle = new Trangle();
                square.ShowInfo();
                circle.ShowInfo();
                trangle.ShowInfo();
                Console.ReadLine();
            }
        }
    
        class Shape
        {
            public double Area { get; set; }
            public string Color { get; set; }
            //使用virtual表明 该函数成员的功能希望在子类中被更新
            public virtual void ShowInfo()
            {
                //既然每个子类都要升级该函数的功能,那就干脆不写任何功能代码了
            }
        }
    
        class Square : Shape
        {
            public override void ShowInfo()
            {
                Console.WriteLine("我有用四条边,且对边相等。。。。");
            }
        }
    
        class Circle : Shape
        {
            public override void ShowInfo()
            {
                Console.WriteLine("我圆形,我特圆....");
            }
        }
    
        class Trangle : Shape
        {
            public override void ShowInfo()
            {
                Console.WriteLine("我是三角形,我具有稳定性....");
            }
        }
    }
    /*输出:
    我有用四条边,且对边相等。。。。
    我圆形,我特圆....
    我是三角形,我具有稳定性....
    */
    

    上面代码演示了,多态的使用;需要注意的是,在基类中ShowInfo方法中的一段注释;这段注释是为了引出抽象类:因为声明了一个virtual函数,而这个函数里面却什么也没做,这看起来是不是很奇怪?下篇文章我将记录专为做基类而生的"抽象类",它就能很好地解决目前我们遇到的"没有功能代码的空函数"的问题。

    以上是对基于继承的重写和多态的总结,记录下来以便以后查阅。

  • 相关阅读:
    vim7.1在windows下的编码设置[转]
    Swashbuckle(6.2.3)【Swagger(3.0)】 第一节
    Git命令集合
    ABP Framework(5.0.0rc) 第一节
    /var/lib/docker/overlay2 占用很大,清理Docker占用的磁盘空间,迁移 /var/lib/docker 目录
    WPF中解决内存泄露的几点提示与解决方法
    用C#读取docx文件
    C#启动单个实例
    WPF学习心得(1):WPF进行动画后不能改变相对应的属性问题的解决
    [转]使WPF程序应用预置的控件风格, 如Aero, Luna, Royale, Classic等
  • 原文地址:https://www.cnblogs.com/bigbosscyb/p/13875645.html
Copyright © 2020-2023  润新知