多态是基于重写的

  • 继承:向子类中添加父类没有的成员,子类对父类的横向扩展
  • 重写:纵向扩展,成员没有增加,但成员的版本增加了

    Override 重写

    子类对父类成员的重写。
    因为类成员个数还是那么多,只是更新版本,所以又称为纵向扩展。
    注:重写时,Car 里面只有一个版本的 Run。

重写需要父类成员标记为 virtual,子类成员标记 override
注:被标记为 override 的成员,隐含也是 virtual 的,可以继续被重写。

virtual:可被重写的、名义上的、名存实亡的

  1. using System;
  2. namespace OverFiceExample
  3. {
  4. class Program
  5. {
  6. static void Main(string[] args)
  7. {
  8. var car = new RaceCar();
  9. car.Run();
  10. }
  11. }
  12. class Vehicle
  13. {
  14. public virtual void Run()
  15. {
  16. Console.WriteLine("I'm running");
  17. }
  18. }
  19. class Car:Vehicle
  20. {
  21. public override void Run()
  22. {
  23. Console.WriteLine("Car is running");
  24. }
  25. }
  26. class RaceCar:Car
  27. {
  28. public override void Run()
  29. {
  30. Console.WriteLine("RaceCar is running");
  31. }
  32. }
  33. }

Hide

如果子类和父类中函数成员签名相同,但又没标记 virtual 和 override,称为 hide 隐藏。
26重写与多态 - 图1
这会导致 Car 类里面有两个 Run 方法,一个是从 Vehicle 继承的 base.Run(),一个是自己声明的 this.Run()。

可以理解为 v 作为 Vehicle 类型,它本来应该顺着继承链往下(一直到 Car)找 Run 的具体实现,但由于 Car 没有 Override,所以它找不下去,只能调用 Vehicle 里面的 Run。

  1. using System;
  2. namespace OverFiceExample
  3. {
  4. class Program
  5. {
  6. static void Main(string[] args)
  7. {
  8. var car = new RaceCar();
  9. car.Run();
  10. // RaceCar is running
  11. var ca = new Car();
  12. ca.Run();
  13. // Car is running
  14. }
  15. }
  16. class Vehicle
  17. {
  18. public virtual void Run()
  19. {
  20. Console.WriteLine("I'm running");
  21. }
  22. }
  23. class Car:Vehicle
  24. {
  25. public override void Run()
  26. {
  27. Console.WriteLine("Car is running");
  28. }
  29. }
  30. class RaceCar:Car
  31. {
  32. public override void Run()
  33. {
  34. Console.WriteLine("RaceCar is running");
  35. }
  36. }
  37. }

注:

  1. 新手不必过于纠结 Override 和 Hide 的区分、关联。因为原则上是不推荐用 Hide 的。很多时候甚至会视 Hide 为一种错误
  2. Java 里面是天然重写,不必加 virtual 和 override,也没有 Hide 这种情况
  3. Java 里面的 @Override(annotation)只起到辅助检查重写是否有效的功能

    Polymorphism 多态

    C# 支持用父类类型的变量引用子类类型的实例。
    函数成员的具体行为(版本)由对象决定。

回顾:因为 C# 语言的变量和对象都是有类型的,就导致存在变量类型与对象类型不一致的情况,所以会有“代差”。

  1. class Program
  2. {
  3. static void Main(string[] args)
  4. {
  5. Vehicle v = new RaceCar();
  6. v.Run();
  7. // Race car is running!
  8. Car c = new RaceCar();
  9. c.Run();
  10. // Race car is running!
  11. Console.ReadKey();
  12. }
  13. }
  14. class Vehicle
  15. {
  16. public virtual void Run()
  17. {
  18. Console.WriteLine("I'm running!");
  19. }
  20. }
  21. class Car : Vehicle
  22. {
  23. public override void Run()
  24. {
  25. Console.WriteLine("Car is running!");
  26. }
  27. }
  28. class RaceCar : Car
  29. {
  30. public override void Run()
  31. {
  32. Console.WriteLine("Race car is running!");
  33. }
  34. }

C# vs Python

Python 是对象有类型,变量没有类型的语言,Python 变量的类型永远跟着对象走。 所以在 Python 中即使重写了,也没有多态的效果。
26重写与多态 - 图2
PS:

  1. JS 和 Python 类似,也是对象有类型,变量没类型
  2. 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

重写属性示例:

  1. class Program
  2. {
  3. static void Main(string[] args)
  4. {
  5. Vehicle v = new Car();
  6. v.Run();
  7. // "Car is running!"
  8. Console.WriteLine(v.Speed);
  9. // 50
  10. }
  11. }
  12. class Vehicle
  13. {
  14. private int _speed;
  15. public virtual int Speed
  16. {
  17. get { return _speed; }
  18. set { _speed = value; }
  19. }
  20. public virtual void Run()
  21. {
  22. Console.WriteLine("I'm running!");
  23. _speed = 100;
  24. }
  25. }
  26. class Car : Vehicle
  27. {
  28. private int _rpm;
  29. public override int Speed
  30. {
  31. get { return _rpm / 100; }
  32. set { _rpm = value * 100; }
  33. }
  34. public override void Run()
  35. {
  36. Console.WriteLine("Car is running!");
  37. _rpm = 5000;
  38. }
  39. }

可见

只有对子类可见的父类成员可以重写,具体说就是 protected 和 public。例如子类能继承父类 private 的成员,但无法访问,即不可见、不可重写。

访问级别的更多内容参考 25类的继承,类成员访问

签名一致

方法签名:方法名称 + 类型形参的个数 + 每个形参(从左往右)的类型和种类(值、引用或输出)。