c#重寫和多型

Pomelo_劉金 發表於 2022-01-20
C#

多型是基於重寫的

  • 繼承:向子類中新增父類沒有的成員,子類對父類的橫向擴充套件
  • 重寫:縱向擴充套件,成員沒有增加,但成員的版本增加了

引言

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

c#重寫和多型

 

 

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。

c#重寫和多型

 

 

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# 語言標準文件

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 隱藏。

c#重寫和多型

 

 

這會導致 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!");
    }
}

 

注:

  1. 新手不必過於糾結 Override 和 Hide 的區分、關聯。因為原則上是不推薦用 Hide 的。很多時候甚至會視 Hide 為一種錯誤
  2. Java 裡面是天然重寫,不必加 virtual 和 override,也沒有 Hide 這種情況
  1. 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 中即使重寫了,也沒有多型的效果。

c#重寫和多型

 

 

PS:

  1. JS 和 Python 類似,也是物件有型別,變數沒型別
  2. TypeScript 是基於 JS 的強型別語言,所以 TS 變數是有型別的,存在多型

重寫三條件

函式成員

只有函式成員才能重寫,最常用的是重寫 Methods 和 Properties。

 

函式成員的定義:

c#重寫和多型

 

 

重寫屬性示例:

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

簽名一致

 

方法簽名:方法名稱 + 型別形參的個數 + 每個形參(從左往右)的型別和種類(值、引用或輸出)。

 

注:下面要講介面和抽象類,為了與本節內容混淆,必須把本節徹底消化吸收。