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

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

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

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

上一篇:win10 shift+鼠标右键 在本地打开cmd


下一篇:StartIsBack——win10恢复win7菜单界面