在上一篇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函数,而这个函数里面却什么也没做,这看起来是不是很奇怪?下篇文章我将记录专为做基类而生的"抽象类",它就能很好地解决目前我们遇到的"没有功能代码的空函数"的问题。
以上是对基于继承的重写和多态的总结,记录下来以便以后查阅。