1. Tuple 命名:為解構提供語境
在上一堂課我們已經認識過 tuple 的命名元素,這讓你的程式碼更好懂,不用再用沒意義的 Item1、Item2,而是用有意義的名字(像是 person.Name)。給 tuple 元素命名特別重要,當它們被用來當作方法、參數或屬性的回傳值時。命名就是讓解構更方便的關鍵前提,這點我們等等會深入聊。
提醒一下,名字可以在 tuple 初始化時用 literal 指定,也可以在方法、屬性或欄位的回傳型別簽名裡指定。
方法範例: 方法回傳值裡的 tuple 命名元素
public static (int 年齡, string 名字) GetPetInfo()
{
return (年齡: 5, 名字: "巴爾西克");
}
// 使用方式:
var info = GetPetInfo();
Console.WriteLine($"{info.名字} — {info.年齡} 歲");
欄位/屬性範例: 欄位/屬性裡的 tuple 命名元素
public (int 寬度, int 高度) ImageSize = (1024, 768);
記得: 如果沒明確指定名字,元素還是可以用 Item1、Item2 這種方式取用。這會讓程式碼比較難懂,尤其 tuple 用到後面時。所以建議只要語意不明顯,就一定要給元素取個有意義的名字。
2. Tuple 解構
什麼是解構?
解構就是把 tuple「拆開」成多個變數,這樣用起來更方便。比如說,從 (年齡: 5, 名字: "巴爾西克") 這個 tuple,可以直接拿到兩個變數:年齡 和 名字。
比喻: 想像 tuple 就像一個有標籤的盒子,解構就是你把盒子裡的東西直接攤開放桌上。
解構語法
var pet = (年齡: 5, 名字: "巴爾西克");
var (age, name) = pet;
Console.WriteLine($"{name} — {age} 歲");
現在我們有兩個變數:age 和 name。它們拿到 tuple 裡的值。左邊的名字(age、name)不用跟 tuple 裡的名字一樣,這只是新的區域變數而已。
解構函式回傳值
很多時候 tuple 都是用來回傳多個值,這時解構就超方便:
public static (double 最小值, double 最大值) GetMinMax(int[] 資料)
{
int 最小值 = 資料.Min();
int 最大值 = 資料.Max();
return (最小值, 最大值);
}
var 數字們 = new[] { 1, 2, 3, 4, 5 };
var (minValue, maxValue) = GetMinMax(數字們);
Console.WriteLine($"最小值: {minValue}, 最大值: {maxValue}");
注意,解構時變數名稱可以根據當下語境自己取。
用 var 解構
var (a, b) = (10, 20); // int a = 10, b = 20
解構和 discard _
有時候 tuple 裡不是每個元素都需要,可以用 _(discard)來忽略。 Discard(_)就是告訴編譯器「我知道這裡有個元素,但我不需要,不要幫我創變數」。
這樣你解構 tuple 時,可以在不需要的地方留個「洞」。超簡潔又不會亂丟沒用的變數!
var pet = (年齡: 5, 名字: "巴爾西克", 開心嗎: true);
var (age, _, isHappy) = pet; // 只要 age 和 isHappy,名字直接忽略
有趣的是:discard 很常用來避免 namespace 裡出現一堆沒用的變數。
在 foreach 裡解構
C# 可以直接在 foreach 裡解構 tuple 陣列:
var pets = new (string 名字, int 年齡)[]
{
("巴爾西克", 5),
("穆西亞", 3),
("約翰尼", 7)
};
foreach (var (name, age) in pets)
{
Console.WriteLine($"{name} — {age} 歲");
}
3. 元素命名和型別的運作方式
命名元素的行為
Tuple 元素的名字只是「語法糖」,也就是寫給人看的。編譯時名字會變成內部欄位 Item1、Item2 等等,但 IDE 和編譯器會保留名字讓你用。
對型別相容性和轉型的影響
兩個 tuple 只要元素數量和型別一樣,不管名字怎麼取,對編譯器來說都是同一種型別。元素名字不會影響型別判斷:
var t1 = (X: 42, Y: 13);
var t2 = (A: 42, B: 13);
t1 = t2; // OK
Console.WriteLine(t1.X); // 42
但在寫表達式和 IntelliSense 提示時,會用左邊(被賦值那邊)的名字,不會用右邊的。
隱式和顯式命名
這之前有提過,再提醒一次:tuple 可以只給部分元素命名,也可以完全不命名——那就會是 Item1 這種預設名字。
var point = (X: 10, 20); // X 和 Item2
Console.WriteLine(point.X); // 10
Console.WriteLine(point.Item2); // 20
建議: 只要 tuple 有超過一兩個元素,或是值的意義不明顯,就一定要幫元素命名。
4. 使用命名和解構時的常見錯誤與細節
錯誤一:「名字搬家」問題
如果你先宣告了一個有名字的 tuple,然後賦值給另一個沒有名字(或名字不同)的 tuple,IntelliSense 還是會顯示原本的名字。像這樣:
var original = (X: 1, Y: 2);
var alias = original; // alias.X == 1, alias.Y == 2
original = (10, 20); // 沒有名字的 tuple
Console.WriteLine(alias.X); // 還是可以用,alias 還是保留舊名字
錯誤二:元素名字重複
不能給兩個元素同樣的名字——編譯器會報「Duplicate tuple element name」錯誤:
var badTuple = (A: 1, A: 2); // 錯誤 CS8122: Duplicate tuple element name 'A'
錯誤三:解構元素數量不符
解構時變數數量一定要跟 tuple 元素數量一樣,不然會報錯:
var pet = (年齡: 5, 名字: "巴爾西克");
var (age, name, mood) = pet; // 錯誤 CS8124: Tuple must contain exactly 3 elements
錯誤四:錯誤使用 discard _
用 _ 忽略元素時,每個位置都要單獨寫,不能「合併」跳過多個。像這樣會錯:
var data = (1, 2, 3);
var (_, x, _) = data; // 正確:跳過第一和第三個元素
var (_, _) = data; // 錯誤 CS8124: Tuple must contain exactly 2 elements
GO TO FULL VERSION