zoukankan      html  css  js  c++  java
  • 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函数,而这个函数里面却什么也没做,这看起来是不是很奇怪?下篇文章我将记录专为做基类而生的"抽象类",它就能很好地解决目前我们遇到的"没有功能代码的空函数"的问题。

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

  • 相关阅读:
    Java8 Stream Function
    PLINQ (C#/.Net 4.5.1) vs Stream (JDK/Java 8) Performance
    罗素 尊重 《事实》
    小品 《研发的一天》
    Java8 λ表达式 stream group by max then Option then PlainObject
    这人好像一条狗啊。什么是共识?
    TOGAF TheOpenGroup引领开发厂商中立的开放技术标准和认证
    OpenMP vs. MPI
    BPMN2 online draw tools 在线作图工具
    DecisionCamp 2019, Decision Manager, AI, and the Future
  • 原文地址:https://www.cnblogs.com/bigbosscyb/p/13875645.html
Copyright © 2011-2022 走看看