CodeGym /課程 /C# SELF /方法多載 (Method Overloading)

方法多載 (Method Overloading)

C# SELF
等級 21 , 課堂 1
開放

1. 前言

在現實生活中,很多動作就像瑞士刀一樣多功能:同一個指令可以搭配不同的工具。例如,想像一下提款機:你插卡時,提款機會問你密碼;你輸入手機號碼時,提款機會等你輸入 SMS 驗證碼。動作都是「驗證使用者」,但方式不一樣。

寫程式時也常遇到這種情況:看起來是同一個操作,但資料型態或參數數量可能不同。比如,我們的方法要對人或動物打招呼,或是要加總兩個、三個,甚至十個整數。

當然,你可以把方法名字都取不一樣:SumTwoSumThreeSumArray。但工程師都很懶(不是說懶是進步的動力嗎),而且這樣寫程式會變得很難讀。

方法多載

方法多載 就是讓同一個方法名稱可以處理不同參數組合的方法。這是多型的一種,但跟繼承沒關係。

方法多載就是可以在同一個 class(或 struct)裡面建立多個同名方法,但參數列表(型態、數量、順序)不一樣。

方法簽章

C# 裡方法簽章就是方法名稱加上參數型態和順序。回傳型態不算在簽章裡!這常常會讓人踩雷(等等會講)。

2. 多載實戰:簡單範例

來寫個 Greeter class,根據不同情況跟使用者打招呼:只用名字、名字加年齡,或是完全沒參數。


public class Greeter
{
    // 沒參數的打招呼
    public void Greet()
    {
        Console.WriteLine("哈囉,世界!");
    }

    // 用名字打招呼
    public void Greet(string name)
    {
        Console.WriteLine($"哈囉,{name}!");
    }

    // 用名字和年齡打招呼
    public void Greet(string name, int age)
    {
        Console.WriteLine($"哈囉,{name}!你已經 {age} 歲了?不錯喔!");
    }
}

現在你可以呼叫任何一個方法,C# 編譯器會自動根據你給的參數數量和型態選對的版本。

var greeter = new Greeter();
greeter.Greet();                // 哈囉,世界!
greeter.Greet("安雅");           // 哈囉,安雅!
greeter.Greet("彼得", 23);      // 哈囉,彼得!你已經 23 歲了?不錯喔!

3. 依參數型態和數量區分

多載會成立,只要方法有下列不同:

  • 參數數量不同,
  • 至少有一個參數型態不同,
  • 參數型態順序不同(但這要小心)。

來加一個只接收年齡的多載版本:


public void Greet(int age)
{
    Console.WriteLine($"這麼多年紀很酷!({age} 歲)");
}

現在呼叫:

greeter.Greet(10);          // 這麼多年紀很酷!(10 歲)

要記得:如果方法只差回傳型態,是不能多載的。例如這樣會出錯:


// 編譯錯誤!
public int Foo(string s) { ... }
public double Foo(string s) { ... }
只靠回傳型態不能多載!

編譯器會抱怨:「已經有 Foo(string) 了,來點有挑戰性的吧!」

4. 多載與 C# 標準函式庫

多載不只是我們自己寫的 Greet。打開 .NET Console.WriteLine 文件

簽章 用途
WriteLine()
印出空字串
WriteLine(string)
印出字串
WriteLine(int)
印出整數
WriteLine(double)
印出小數
WriteLine(string, object)
格式化字串(帶一個參數)
WriteLine(string, params object[])
格式化字串(多個參數)

這些都是同一個方法 WriteLine 的多載。這就是為什麼你可以這樣寫:

Console.WriteLine("純字串");
Console.WriteLine(123);
Console.WriteLine(2.5);
Console.WriteLine("總和: {0}", 42);

編譯器都能正確判斷你要哪個版本!

5. 編譯器怎麼選多載?

很嚴格:它會看你實際傳進去的參數型態和數量。來個小表格:

呼叫 會選哪個版本?
greeter.Greet()
void Greet()
greeter.Greet(75)
void Greet(int)

遇到模糊怎麼辦?

有時候會失控。多載太像時,編譯器選不出來:


public void Print(int a, double b) { ... }
public void Print(double a, int b) { ... }

printer.Print(5, 10); 
// 錯誤:模糊不清——到底要叫哪個 Print?(兩個都好像可以)

編譯器會報模糊錯誤。這種情況最好避免參數數量一樣、型態又很像的多載,不然會讓編譯器抓狂。

6. params —— 變數參數數量

假設你想寫個方法,可以接收不定數量的數字。這時就要用 params 關鍵字。


public void SumAll(params int[] numbers)
{
    int sum = 0;
    foreach (int n in numbers)
        sum += n;
    Console.WriteLine($"總和: {sum}");
}
params 的變數參數方法

你可以這樣呼叫:

SumAll(1, 2, 3);           // 總和: 6
SumAll(10, 20);            // 總和: 30
SumAll();                  // 總和: 0

params 的方法可以跟多載一起用,但要小心不要讓編譯器搞不清楚你想叫哪個版本。

7. 多載與參數修飾詞 (ref, out, in)

C# 會把參數修飾詞算進方法簽章(也就是 void Foo(int a)void Foo(ref int a) 是不同方法,可以同時存在):

public void SetValue(int a)
{
    a = 42;
}

public void SetValue(ref int a)
{
    a = 100;
}
ref 修飾詞多載

沒用 ref 會叫到第一個,有用 ref 會叫到第二個:

int n = 5;
SetValue(n);     // n 還是 5(值被複製)
SetValue(ref n); // n 變成 100

8. 圖解:什麼是多載


      +----------+
      |  MyClass |
      +----------+
           |
           |            (方法片段)
    +-----------------------+
    |   void Foo()          |
    |   void Foo(int a)     |
    |   void Foo(string s)  |
    |   void Foo(int a, int b) |
    +-----------------------+
class 裡的方法多載圖解

如果用程式碼來看:

// 呼叫多載的 Foo() 方法:
var mc = new MyClass();
mc.Foo();              // void Foo()
mc.Foo(5);             // void Foo(int)
mc.Foo("Hello");       // void Foo(string)
mc.Foo(2, 3);          // void Foo(int, int)

9. 範例:在我們的應用程式多載方法

來繼續我們的學習專案,幫動物繼承樹加上多載方法。


public class Animal
{
    public string Name { get; set; }

    // 發出聲音的方法
    public virtual void MakeSound()
    {
        Console.WriteLine("某種奇怪的聲音...");
    }

    // 多載方法:指定音量的聲音
    public void MakeSound(int volume)
    {
        Console.WriteLine($"動物發出 {volume} 分貝的聲音。");
    }
}

public class Dog : Animal
{
    public override void MakeSound()
    {
        Console.WriteLine("汪!");
    }

    // 多載方法:指定音量的汪汪
    public void MakeSound(int volume)
    {
        Console.WriteLine($"汪!(音量: {volume} 分貝)");
    }
}

可以這樣呼叫:

Dog rex = new Dog();
rex.MakeSound();           // 汪!
rex.MakeSound(75);         // 汪!(音量: 75 分貝)

注意:在子類別 (Dog) 裡我們多載了 MakeSound(int volume),所以現在有兩個版本:有參數跟沒參數。

10. 多載常見錯誤

錯誤一:只靠回傳型態多載。
這不行——回傳型態不算在方法簽章裡。多載必須靠輸入參數的數量或型態,不是 voidint

錯誤二:模糊多載讓編譯器抓狂。
參數數量一樣、型態很像(例如 intdouble)會讓編譯器搞不清楚。像 Print(int a, double b)Print(double a, int b),呼叫 Print(1, 1) 就會報模糊錯誤。

錯誤三:params 跟其他多載衝突。
params 的方法可能會搶走本來要給其他多載的呼叫。如果型態一樣,編譯器可能選錯版本。

錯誤四:忘了 refout 也算簽章。
Do(ref int x)Do(out int x) 是不同多載。如果沒注意,很容易叫錯版本。

留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION