多态是基于重写的

  • 继承:向子类中添加父类没有的成员,子类对父类的横向扩展

  • 重写:纵向扩展,成员没有增加,但成员的版本增加了

引言

Rider

JetBrains:Rider、ReSharper、dotPeek

Rider 支持包括 .NET Core 在内的较全面的 .NET 开发,以及 Unity 开发。
.NET Core / ASP.NETWeb Application 开发包括:

  1. RestFul API

  2. .NET Core 网站开发

Q:Rider 是否支持 WinForm 开发?
A:不支持 WinForm,但支持基于 XAML 的 WPF 和 Xamarin
026 重写与多态 - 图1
Java 常用 Spring + Hibernate + JPA 这一套,它们都能在 .NET Core 找到对应选择。
ASP.NET Core 框架 + Entity Framework Core
.NET Core 自带 Razor engine

Edx Timothy 参与开发的课程

校长关于 ASP.NET Core 开发的一些课程,分别讲 LINQ、Web 开发基础、RestFul API、实战。
未来还将有一门 ASP.NET Core 高级开发和 Authentication & Authorization。
026 重写与多态 - 图2

DFS 与 BFS

  • DFS:Depth-First-Search 深度优先搜索

  • BFS:Breadth-First Search 广度优先搜索

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. namespace ConsoleTemp
  5. {
  6. // 注:为了方便理解,很多变量命名都用的全称
  7. class Program
  8. {
  9. static void Main(string[] args)
  10. {
  11. // 生成 [0,10) 的自然数数组,即 0,1,2,3...9
  12. var values = Enumerable.Range(0, 10).ToArray();
  13. var binarySearchTree = GetTree(values, 0, values.Length - 1);
  14. DFS(binarySearchTree);
  15. Console.WriteLine("============================");
  16. BFS(binarySearchTree);
  17. }
  18. static Node GetTree(int[] values, int lowIndex, int highIndex)
  19. {
  20. if (lowIndex > highIndex) return null;
  21. var middleIndex = lowIndex + (highIndex - lowIndex) / 2;
  22. var node = new Node(values[middleIndex]);
  23. node.Left = GetTree(values, lowIndex, middleIndex - 1);
  24. node.Right = GetTree(values, middleIndex + 1, highIndex);
  25. return node;
  26. }
  27. static void DFS(Node node)
  28. {
  29. if (node == null) return;
  30. DFS(node.Left);
  31. Console.WriteLine(node.Value);
  32. DFS(node.Right);
  33. }
  34. static void BFS(Node root)
  35. {
  36. var q = new Queue<Node>();
  37. q.Enqueue(root);
  38. while (q.Count > 0)
  39. {
  40. var node = q.Dequeue();
  41. Console.WriteLine(node.Value);
  42. if (node.Left != null) q.Enqueue(node.Left);
  43. if (node.Right != null) q.Enqueue(node.Right);
  44. }
  45. }
  46. }
  47. class Node
  48. {
  49. public int Value { get; set; }
  50. public Node Left { get; set; }
  51. public Node Right { get; set; }
  52. public Node(int value)
  53. {
  54. Value = value;
  55. }
  56. }
  57. }

026 重写与多态 - 图3

C# 语言标准文档

C# 5.0 已经成为国际标准 ECMA-334,ECMA-334 的 PDF 比微软自己的标准文档还要权威。
C# 6.0 7.0 还在 ECMA 验证中。
笔者注:校长还是很注重标准文档,推荐有志于深入 C# 的同学去多翻一翻、读一读。


下面开始讲解本节的正式内容:

本节内容

  • 类的继承
    • 类成员的“横向扩展”(成员越来越多)
    • 类成员的“纵向扩展”(行为改变,版本增高)
    • 类成员的隐藏(不常用)
    • 重写与隐藏的发生条件:函数成员,可见,签名一致
  • 多态(polymorphism)
    • 基于重写机制(virtual -> override)
    • 函数成员的具体行为(版本)由对象决定
    • 回顾:C# 语言的变量和对象都是有类型的,所以会有“代差”

      Override 重写

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

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

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

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

Hide

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

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

  1. class Program
  2. {
  3. static void Main(string[] args)
  4. {
  5. Vehicle v = new Car();
  6. v.Run();
  7. // I'm running!
  8. }
  9. }
  10. class Vehicle
  11. {
  12. public void Run()
  13. {
  14. Console.WriteLine("I'm running!");
  15. }
  16. }
  17. class Car : Vehicle
  18. {
  19. public void Run()
  20. {
  21. Console.WriteLine("Car is running!");
  22. }
  23. }

注:

  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 中即使重写了,也没有多态的效果。
026 重写与多态 - 图5
PS:

  1. JS 和 Python 类似,也是对象有类型,变量没类型

  2. TypeScript 是基于 JS 的强类型语言,所以 TS 变量是有类型的,存在多态

重写三条件

函数成员

只有函数成员才能重写,最常用的是重写 Methods 和 Properties。

函数成员的定义:
image.png
重写属性示例:

  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 的成员,但无法访问,即不可见、不可重写。

访问级别的更多内容参考 024,025 类的声明,继承和访问控制

签名一致

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

注:下面要讲接口和抽象类,为了与本节内容混淆,必须把本节彻底消化吸收。