今天來詳細解析C#中這個核心且強大的概念——多態。這就像是我們編程工具箱裏的“瑞士軍刀”,用好了能讓代碼變得異常靈活和優雅。

一、多態是什麼?一個生動的比喻

想象一下一個通用的“繪圖”指令。

  • 你對一個畫家説“繪圖”,他可能會拿出一張畫布,開始畫一幅油畫。
  • 你對一個建築師説“繪圖”,他可能會打開電腦,用CAD軟件繪製建築藍圖。
  • 你對一個孩子説“繪圖”,他可能會拿出蠟筆,在紙上塗鴉。

同一個“繪圖”指令,不同身份的對象會執行出完全不同的行為。這就是多態的精髓!

在C#中,多態是指:同一個操作(通常是方法調用)作用於不同的對象(這些對象屬於同一個繼承體系),可以有不同的解釋,產生不同的執行結果。

二、C#實現多態的兩種主要形式

C#主要通過兩種機制來實現多態:

  1. 編譯時多態(靜態多態):在編譯時就知道具體調用哪個方法。主要通過方法重載實現。
  2. 運行時多態(動態多態):在程序運行時才確定調用哪個方法。主要通過繼承方法重寫實現,這是面向對象中最重要的多態形式。

三、編譯時多態:方法重載

概念:在同一個類中,允許存在多個同名方法,但它們的參數列表必須不同(參數類型、個數或順序不同)。編譯器在編譯時根據傳入的參數來決定具體調用哪個方法。

舉例説明:

public class Calculator
{
    // 1. 兩個整數相加
    public int Add(int a, int b)
    {
        Console.WriteLine("調用整數相加方法");
        return a + b;
    }

    // 2. 三個整數相加 (參數個數不同)
    public int Add(int a, int b, int c)
    {
        Console.WriteLine("調用三個整數相加方法");
        return a + b + c;
    }

    // 3. 兩個浮點數相加 (參數類型不同)
    public double Add(double a, double b)
    {
        Console.WriteLine("調用浮點數相加方法");
        return a + b;
    }

    // 4. 字符串連接 (參數類型不同)
    public string Add(string a, string b)
    {
        Console.WriteLine("調用字符串連接方法");
        return a + b;
    }
}

class Program
{
    static void Main(string[] args)
    {
        Calculator calc = new Calculator();

        // 編譯器在編譯時就能確定調用哪個重載方法
        int result1 = calc.Add(5, 10);        // 調用第一個Add
        int result2 = calc.Add(1, 2, 3);      // 調用第二個Add
        double result3 = calc.Add(2.5, 3.7);  // 調用第三個Add
        string result4 = calc.Add("Hello, ", "World!"); // 調用第四個Add

        Console.WriteLine(result1); // 輸出:15
        Console.WriteLine(result2); // 輸出:6
        Console.WriteLine(result3); // 輸出:6.2
        Console.WriteLine(result4); // 輸出:Hello, World!
    }
}

核心要點

  • 返回值類型不同不能構成重載。
  • 決策發生在編譯時

四、運行時多態:繼承與方法重寫

這是多態最核心、最強大的部分。它允許我們使用父類的引用來指向子類的對象,並在運行時調用子類重寫的方法。

實現它需要三個關鍵字:virtual, override, 和 base

舉例説明:一個圖形繪製系統

// 1. 基類:定義抽象概念和可重寫的行為
public class Shape
{
    // 使用 virtual 關鍵字標記,表示這個方法可以被子類重寫
    public virtual void Draw()
    {
        Console.WriteLine("繪製一個圖形...");
    }

    // 計算面積的方法也定義為 virtual,因為不同圖形計算方式不同
    public virtual double CalculateArea()
    {
        return 0;
    }
}

// 2. 派生類:實現具體的行為
public class Circle : Shape
{
    public double Radius { get; set; }

    public Circle(double radius)
    {
        Radius = radius;
    }

    // 使用 override 關鍵字,重寫父類的Draw方法
    public override void Draw()
    {
        Console.WriteLine($"繪製一個圓形,半徑為:{Radius}");
        // 這裏可以想象有複雜的繪製圓形的代碼
    }

    public override double CalculateArea()
    {
        return Math.PI * Radius * Radius;
    }
}

public class Rectangle : Shape
{
    public double Width { get; set; }
    public double Height { get; set; }

    public Rectangle(double width, double height)
    {
        Width = width;
        Height = height;
    }

    // 同樣重寫Draw方法,但實現完全不同
    public override void Draw()
    {
        Console.WriteLine($"繪製一個矩形,寬:{Width},高:{Height}");
    }

    public override double CalculateArea()
    {
        return Width * Height;
    }
}

public class Triangle : Shape
{
    public double Base { get; set; }
    public double Height { get; set; }

    public Triangle(double @base, double height)
    {
        Base = @base;
        Height = height;
    }

    public override void Draw()
    {
        Console.WriteLine($"繪製一個三角形,底:{Base},高:{Height}");
    }

    public override double CalculateArea()
    {
        return 0.5 * Base * Height;
    }
}

現在,見證多態的魔力:

class Program
{
    static void Main(string[] args)
    {
        // 創建一個Shape類型的列表,但裏面可以存放任何派生類的對象
        List<Shape> shapes = new List<Shape>
        {
            new Circle(5.0),
            new Rectangle(4.0, 6.0),
            new Triangle(3.0, 4.0),
            new Circle(2.5)
        };

        Console.WriteLine("開始繪製所有圖形:\n");

        // 多態的核心體現!
        foreach (Shape shape in shapes) // 編譯時類型是 Shape
        {
            // 運行時,CLR會檢查shape實際指向的對象類型,並調用該類型的Draw方法
            shape.Draw(); // 運行時類型是 Circle, Rectangle, Triangle...
            double area = shape.CalculateArea();
            Console.WriteLine($"該圖形的面積是:{area:F2}\n");
        }
    }
}

輸出結果:

開始繪製所有圖形:

繪製一個圓形,半徑為:5
該圖形的面積是:78.54

繪製一個矩形,寬:4,高:6
該圖形的面積是:24.00

繪製一個三角形,底:3,高:4
該圖形的面積是:6.00

繪製一個圓形,半徑為:2.5
該圖形的面積是:19.63

五、多態的優勢與總結

  1. 可擴展性:如果要新增一個 Hexagon(六邊形)類,只需讓它繼承 Shape 並重寫 Draw()CalculateArea() 方法。主循環的代碼一行都不用改! 這符合“開閉原則”(對擴展開放,對修改關閉)。
  2. 可替換性:子類對象可以無縫替換父類對象。
  3. 接口統一:為不同的類提供了統一的接口(例如 Draw 方法),使得代碼更易於理解和維護。你不需要寫一堆 if (shape is Circle) ... else if (shape is Rectangle) ... 這樣冗長且難以維護的代碼。
  4. 解耦:調用代碼只依賴於抽象的基類 Shape,而不依賴於具體的 Circle, Rectangle 等實現類,降低了代碼的耦合度。

關鍵術語回顧:

  • virtual:在基類中聲明一個方法可以被重寫。
  • override:在派生類中提供該方法的新實現。
  • base:在派生類中,用於調用基類已被重寫的方法或構造函數。
  • 編譯時類型 vs 運行時類型Shape myShape = new Circle(); 中,myShape 的編譯時類型是 Shape,運行時類型是 Circle。多態方法調用取決於運行時類型

這個詳細的解釋和示例能讓你對C#的多態有深刻的理解。它是寫出高質量、可維護C#代碼的基石,一定要多加練習和使用!