多型是基於重寫的
- 繼承:向子類中新增父類沒有的成員,子類對父類的橫向擴充套件
- 重寫:縱向擴充套件,成員沒有增加,但成員的版本增加了
引言
Rider
JetBrains:Rider、ReSharper、dotPeek
Rider 支援包括 .NET Core 在內的較全面的 .NET 開發,以及 Unity 開發。
.NET Core / ASP.NETWeb Application 開發包括:
- RestFul API
- .NET Core 網站開發
Q:Rider 是否支援 WinForm 開發?
A:不支援 WinForm,但支援基於 XAML 的 WPF 和 Xamarin
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。
DFS 與 BFS
- DFS:Depth-First-Search 深度優先搜尋
- BFS:Breadth-First Search 廣度優先搜尋
using System; using System.Collections.Generic; using System.Linq; namespace ConsoleTemp { // 注:為了方便理解,很多變數命名都用的全稱 class Program { static void Main(string[] args) { // 生成 [0,10) 的自然數陣列,即 0,1,2,3...9 var values = Enumerable.Range(0, 10).ToArray(); var binarySearchTree = GetTree(values, 0, values.Length - 1); DFS(binarySearchTree); Console.WriteLine("============================"); BFS(binarySearchTree); } static Node GetTree(int[] values, int lowIndex, int highIndex) { if (lowIndex > highIndex) return null; var middleIndex = lowIndex + (highIndex - lowIndex) / 2; var node = new Node(values[middleIndex]); node.Left = GetTree(values, lowIndex, middleIndex - 1); node.Right = GetTree(values, middleIndex + 1, highIndex); return node; } static void DFS(Node node) { if (node == null) return; DFS(node.Left); Console.WriteLine(node.Value); DFS(node.Right); } static void BFS(Node root) { var q = new Queue<Node>(); q.Enqueue(root); while (q.Count > 0) { var node = q.Dequeue(); Console.WriteLine(node.Value); if (node.Left != null) q.Enqueue(node.Left); if (node.Right != null) q.Enqueue(node.Right); } } } class Node { public int Value { get; set; } public Node Left { get; set; } public Node Right { get; set; } public Node(int value) { Value = value; } } }
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:可被重寫的、名義上的、名存實亡的
class Program { static void Main(string[] args) { var car = new Car(); car.Run(); // Car is running! var v = new Vehicle(); v.Run(); // I'm running! } } class Vehicle { public virtual void Run() { Console.WriteLine("I'm running!"); } } class Car : Vehicle { public override void Run() { Console.WriteLine("Car is running!"); } }
Hide
如果子類和父類中函式成員簽名相同,但又沒標記 virtual 和 override,稱為 hide 隱藏。
這會導致 Car 類裡面有兩個 Run 方法,一個是從 Vehicle 繼承的 base.Run(),一個是自己宣告的 this.Run()。
可以理解為 v 作為 Vehicle 型別,它本來應該順著繼承鏈往下(一直到 Car)找 Run 的具體實現,但由於 Car 沒有 Override,所以它找不下去,只能呼叫 Vehicle 裡面的 Run。
class Program { static void Main(string[] args) { Vehicle v = new Car(); v.Run(); // I'm running! } } class Vehicle { public void Run() { Console.WriteLine("I'm running!"); } } class Car : Vehicle { public void Run() { Console.WriteLine("Car 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。
函式成員的定義:
重寫屬性示例:
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 的成員,但無法訪問,即不可見、不可重寫。
訪問級別的更多內容參考 https://www.yuque.com/yuejiangliu/dotnet/timothy-csharp-024-025#c75846f4。
簽名一致
方法簽名:方法名稱 + 型別形參的個數 + 每個形參(從左往右)的型別和種類(值、引用或輸出)。
注:下面要講介面和抽象類,為了與本節內容混淆,必須把本節徹底消化吸收。