1. 前言
假設我們有一個 class Person:
public class Person
{
public string Name;
public int Age;
public Person(string name, int age)
{
Name = name;
Age = age;
}
}
你現在感覺自己快變天才了——可以創建任何名字和年齡的人。但有時候資訊不夠啊!App 的使用者只輸入了名字,年齡之後才知道。或者你有時候想給個「預設」年齡。當然可以想些 workaround,但 C# 給你一個超優雅的解法——建構子多載。
建構子多載 就是說一個 class 可以有多個同名建構子(就是 class 名),但參數列表不一樣。
這些參數列表有什麼差別? 可以差在:
- 參數數量: 比方說一個建構子收 2 個參數,另一個收 3 個。
- 參數型別: 一個收 (string, int),另一個收 (string, decimal)。
- 參數順序: (string, int) 跟 (int, string) 是不同的 signature。
最重要的規則:compiler 會根據signature 來分辨建構子(還有任何 method)。Signature 包含建構子(或 method)名稱和參數列表(數量、型別、順序)。存取修飾詞(public、private)還有一般 method 的回傳型別都不算 signature 的一部分。但建構子沒有回傳型別,名稱也一定跟 class 一樣。
2. 建構子多載:語法跟範例
public class Cat
{
public string Name;
public string Color;
// 無參數建構子
public Cat()
{
Name = "無名貓";
Color = "灰色";
}
// 一個參數的建構子
public Cat(string name)
{
Name = name;
Color = "灰色";
}
// 兩個參數的建構子
public Cat(string name, string color)
{
Name = name;
Color = color;
}
}
用法:
Cat barsik = new Cat(); // "無名貓", "灰色"
Cat murzik = new Cat("Murzik"); // "Murzik", "灰色"
Cat ryzhik = new Cat("Ryzhik", "橘色"); // "Ryzhik", "橘色"
你在 new 物件時有給參數,C# 會根據參數數量跟型別「猜」你要哪個建構子。
超方便!用你 class 的人不用填他不知道的參數:只想要一隻貓——用無參數建構子,知道名字——用另一個,有顏色需求——用第三個。
3. 多載建構子的呼叫怎麼運作?
C# 會在 compile 階段自動選對的建構子,根據你傳的參數型別和數量。很難搞錯——如果你搞錯,compiler 馬上兇你。
來加個只收 int 參數的建構子:
public Cat(int age)
{
Name = "無名貓";
Color = "灰色";
// 這裡可以加年齡的邏輯
}
現在你還可以「用年齡創貓」:
Cat oldCat = new Cat(5); // 會呼叫 Cat(int age) 這個建構子
| 建構子呼叫 | 會呼叫哪個? |
|---|---|
|
無參數 |
|
一個 string 參數 |
|
兩個 string 參數 |
|
一個 int 參數 |
4. 建構子內部呼叫:this 關鍵字
有時候不同建構子會做很像(甚至一樣)的事。為了不複製貼上,可以「一個建構子呼叫另一個」。這時就用 this。
public class Cat
{
public string Name;
public string Color;
public Cat() : this("無名貓")
{
// 這個建構子會呼叫 Cat(string name)
}
public Cat(string name) : this(name, "灰色")
{
// 這個建構子會呼叫 Cat(string name, string color)
}
public Cat(string name, string color)
{
Name = name;
Color = color;
}
}
結果:
- new Cat() 會呼叫 Cat(string name),然後再呼叫 Cat(string name, string color)。
- 所有「路都通羅馬」:也就是所有初始化都集中在一個「主建構子」。
這種寫法叫 constructor chaining(建構子串接)。
5. 生活中的例子
來寫個遊戲裡的英雄 class(Hero),有名字跟等級!這樣寫:
只有一個建構子:
public class Hero
{
public string Name;
public int Level;
public Hero(string name, int level)
{
Name = name;
Level = level;
}
}
現在讓我們可以用不同方式創英雄!
多個建構子:
public class Hero
{
public string Name;
public int Level;
// 如果沒給,預設是「無名」1級
public Hero() : this("無名", 1)
{
}
// 只知道名字,等級預設 1
public Hero(string name) : this(name, 1)
{
}
// 最主要的建構子,兩個參數
public Hero(string name, int level)
{
Name = name;
Level = level;
}
}
現在可以這樣創英雄:
Hero h1 = new Hero(); // "無名", 1
Hero h2 = new Hero("Arthur"); // "Arthur", 1
Hero h3 = new Hero("Laura", 10); // "Laura", 10
6. 實作細節:隱藏的地雷跟小細節
第一: 如果你自己寫了任何一個建構子,compiler 就不會自動幫你生「空建構子」。所以如果你只寫了有參數的建構子,沒寫無參數的話,像這樣:
Hero h = new Hero(); // 沒有無參數建構子會報錯!
會直接 compile error。
第二: 如果串接建構子時呼叫錯了,初始化邏輯就會亂掉。要保持一致性:最好所有實際初始化都在最完整的建構子,其他都用 this(...) 傳資料進去。
第三: 如果參數只差型別(像 Cat(string s) 跟 Cat(object o)),你用 null 當參數時可能會搞混。compiler 不一定知道你想要哪個建構子。
7. 多載跟欄位初始化
class 裡常常有些欄位一定要給值。建構子多載可以讓你很方便地做到,但預設值要你自己決定什麼最合理。
加個檢查的範例:
public class Book
{
public string Title;
public int Year;
// 預設書名「無標題」,年份 2000
public Book() : this("無標題", 2000)
{
}
public Book(string title) : this(title, 2000)
{
}
public Book(string title, int year)
{
Title = title;
Year = year;
}
}
8. 多載有不同參數組合
有些 class 建構子多到數十個!這不一定是好設計(有時候用「設定」method 或 builder pattern 會更好),但對於 user object、model、DTO——這樣很實用。
範例:class「寵物」,你可以全都指定,也可以只給必要的。
public class Pet
{
public string Name;
public int Age;
public string Type;
public bool IsVaccinated;
// 預設建構子
public Pet() : this("NoName", 0, "Cat", false) { }
// 最少資訊
public Pet(string name, string type) : this(name, 0, type, false) { }
// 完整建構子
public Pet(string name, int age, string type, bool isVaccinated)
{
Name = name;
Age = age;
Type = type;
IsVaccinated = isVaccinated;
}
}
方法多載跟建構子多載的差別
| 一般方法 | 建構子(包含多載) |
|---|---|
| 隨時都能呼叫 | 只有 new 物件時會呼叫(new) |
| 可以回傳任何型別 | 永遠不回傳值(不用寫型別) |
| 方法名稱隨便取,通常用動詞 | 名稱一定跟 class 一樣 |
| 可以用參數多載 | 建構子也是用參數多載 |
GO TO FULL VERSION