多态是基于重写的
- 继承:向子类中添加父类没有的成员,子类对父类的横向扩展
- 重写:纵向扩展,成员没有增加,但成员的版本增加了
Override 重写
子类对父类成员的重写。
因为类成员个数还是那么多,只是更新版本,所以又称为纵向扩展。
注:重写时,Car 里面只有一个版本的 Run。
重写需要父类成员标记为 virtual,子类成员标记 override。
注:被标记为 override 的成员,隐含也是 virtual 的,可以继续被重写。
virtual:可被重写的、名义上的、名存实亡的
using System;
namespace OverFiceExample
{
class Program
{
static void Main(string[] args)
{
var car = new RaceCar();
car.Run();
}
}
class Vehicle
{
public virtual void Run()
{
Console.WriteLine("I'm running");
}
}
class Car:Vehicle
{
public override void Run()
{
Console.WriteLine("Car is running");
}
}
class RaceCar:Car
{
public override void Run()
{
Console.WriteLine("RaceCar is running");
}
}
}
Hide
如果子类和父类中函数成员签名相同,但又没标记 virtual 和 override,称为 hide 隐藏。
这会导致 Car 类里面有两个 Run 方法,一个是从 Vehicle 继承的 base.Run(),一个是自己声明的 this.Run()。
可以理解为 v 作为 Vehicle 类型,它本来应该顺着继承链往下(一直到 Car)找 Run 的具体实现,但由于 Car 没有 Override,所以它找不下去,只能调用 Vehicle 里面的 Run。
using System;
namespace OverFiceExample
{
class Program
{
static void Main(string[] args)
{
var car = new RaceCar();
car.Run();
// RaceCar is running
var ca = new Car();
ca.Run();
// Car is running
}
}
class Vehicle
{
public virtual void Run()
{
Console.WriteLine("I'm running");
}
}
class Car:Vehicle
{
public override void Run()
{
Console.WriteLine("Car is running");
}
}
class RaceCar:Car
{
public override void Run()
{
Console.WriteLine("RaceCar is running");
}
}
}
注:
- 新手不必过于纠结 Override 和 Hide 的区分、关联。因为原则上是不推荐用 Hide 的。很多时候甚至会视 Hide 为一种错误
- Java 里面是天然重写,不必加 virtual 和 override,也没有 Hide 这种情况
- Java 里面的 @Override(annotation)只起到辅助检查重写是否有效的功能
Polymorphism 多态
C# 支持用父类类型的变量引用子类类型的实例。
函数成员的具体行为(版本)由对象决定。
回顾:因为 C# 语言的变量和对象都是有类型的,就导致存在变量类型与对象类型不一致的情况,所以会有“代差”。
class Program
{
static void Main(string[] args)
{
Vehicle v = new RaceCar();
v.Run();
// Race car is running!
Car c = new RaceCar();
c.Run();
// Race car is running!
Console.ReadKey();
}
}
class Vehicle
{
public virtual void Run()
{
Console.WriteLine("I'm running!");
}
}
class Car : Vehicle
{
public override void Run()
{
Console.WriteLine("Car is running!");
}
}
class RaceCar : Car
{
public override void Run()
{
Console.WriteLine("Race car is running!");
}
}
C# vs Python
Python 是对象有类型,变量没有类型的语言,Python 变量的类型永远跟着对象走。 所以在 Python 中即使重写了,也没有多态的效果。
PS:
- JS 和 Python 类似,也是对象有类型,变量没类型
- TypeScript 是基于 JS 的强类型语言,所以 TS 变量是有类型的,存在多态
重写三条件
函数成员
只有函数成员才能重写,最常用的是重写 Methods 和 Properties。
函数成员的定义:
Function members are members that contain executable statements.Function members are always members of types and can not be members of namespaces.C# defines the following categories of function members:
- Methods
- Properties
- Events
- Indexers
- User-defined operators
- Instance constructors
- Static constructors
- Finalizers
重写属性示例:
class Program
{
static void Main(string[] args)
{
Vehicle v = new Car();
v.Run();
// "Car is running!"
Console.WriteLine(v.Speed);
// 50
}
}
class Vehicle
{
private int _speed;
public virtual int Speed
{
get { return _speed; }
set { _speed = value; }
}
public virtual void Run()
{
Console.WriteLine("I'm running!");
_speed = 100;
}
}
class Car : Vehicle
{
private int _rpm;
public override int Speed
{
get { return _rpm / 100; }
set { _rpm = value * 100; }
}
public override void Run()
{
Console.WriteLine("Car is running!");
_rpm = 5000;
}
}
可见
只有对子类可见的父类成员可以重写,具体说就是 protected 和 public。例如子类能继承父类 private 的成员,但无法访问,即不可见、不可重写。
访问级别的更多内容参考 25类的继承,类成员访问。
签名一致
方法签名:方法名称 + 类型形参的个数 + 每个形参(从左往右)的类型和种类(值、引用或输出)。