小提示
剛開始開發軟體嗎? 先從 入門 教學開始。 當你需要從某個方法回傳多個值或組合值,但又不想定義命名型別時,就會遇到元組。
有其他語言的經驗嗎? C# 元組是類似 Python 或 Swift 元組的值型別,但具備可選的命名元素及完整解構支援。 略讀 解構 與 等式 部分,尋找 C# 專用的模式。
元組將多個值分組成一個輕量級結構,無需定義具名型別。 元組是值型別,你可以內聯宣告、從方法回傳,並拆解成個別變數。 當你需要快速、暫時地將相關值分組時,可以使用元組。 例如,當你從一個方法回傳多個結果或儲存一對座標時。
以下範例建立一個包含命名元素的元組,並以名稱存取每個元素:
var location = (Latitude: 47.6062, Longitude: -122.3321);
Console.WriteLine($"Location: {location.Latitude}, {location.Longitude}");
// Output: Location: 47.6062, -122.3321
元組對於短命的群組很有效,因為定義類別、結構或記錄會增加不必要的儀式。 對於具有行為的長壽命領域概念或類型,偏好 記錄、 類別或 結構。 關於何時使用每種類型,請參見 「選擇哪種類型」。
宣告並初始化元組
宣告元組時,請在括號內列出元素類型。 你可以選擇為每個元素命名,讓程式碼更易讀:
// Tuple with named elements
(string Name, int Age) person = ("Alice", 30);
Console.WriteLine($"{person.Name} is {person.Age} years old");
// Tuple with default element names (Item1, Item2)
(string, int) unnamed = ("Bob", 25);
Console.WriteLine($"{unnamed.Item1} is {unnamed.Item2} years old");
// Tuple declared with var and inline names
var city = (Name: "Seattle", Population: 749_256);
Console.WriteLine($"{city.Name}: population {city.Population}");
當你不提供名稱時,元素會使用預設名稱 Item1、 , Item2等等。 命名元素讓你的程式碼能自我文件化,不需要另外的型別定義。
推斷元素名稱
編譯器會從你用來初始化元組的變數名稱或屬性名稱推斷元素名稱。 此功能避免了名稱相符時重複:
var name = "Carol";
var age = 28;
// The compiler infers element names from the variable names
var person = (name, age);
Console.WriteLine($"{person.name} is {person.age}");
// Output: Carol is 28
推斷名稱能讓你的程式碼保持簡潔。 如果你需要不同的元素名稱,請明確指定。
從一個方法回傳多個值
元組最常見的用途之一是從一個方法回傳多個值。 與其定義類別或使用 out 參數,不如回傳一個帶有命名元素的元組:
static (double Minimum, double Maximum, double Average) ComputeStats(List<double> values)
{
var min = values.Min();
var max = values.Max();
var avg = values.Average();
return (min, max, avg);
}
命名元組的元素使返回值在呼叫點和方法簽名上更具可讀性。 呼叫者可以依名稱存取每個值,無需記住位置順序。
解構元組
解構 將元組的元素拆包成單一語句中的獨立變數。 你可以用幾種方式拆解元組:
var point = (X: 3, Y: 7);
// Deconstruct with var (infer all types)
var (x, y) = point;
Console.WriteLine($"x={x}, y={y}");
// Deconstruct with explicit types
(int px, int py) = point;
Console.WriteLine($"px={px}, py={py}");
// Deconstruct into existing variables
int a, b;
(a, b) = point;
Console.WriteLine($"a={a}, b={b}");
// Deconstruct a method return value directly
List<double> data = [10.0, 20.0, 30.0];
var (min, max, avg) = ComputeStats(data);
Console.WriteLine($"Min: {min}, Max: {max}, Avg: {avg}");
當你透過方法呼叫收到元組,並需要立即處理其個別值時,解構特別有用。
你可以直接在 foreach 迴圈中拆解元組,這使得對分組值的組合進行迭代變得簡潔:
List<(string Name, int Score)> results =
[
("Alice", 92),
("Bob", 87),
("Carol", 95)
];
foreach (var (name, score) in results)
{
Console.WriteLine($"{name}: {score}");
}
當你不需要每個元素時,可以用 丟棄 (_)來替代你想忽略的每個值。 為每個丟棄位置單獨使用一個 _。
List<double> values = [5.0, 10.0, 15.0];
var (_, max, _) = ComputeStats(values);
Console.WriteLine($"Only need the max: {max}");
// Output: Only need the max: 15
關於在不同情境中使用棄牌的更多資訊,請參見 棄牌。
元組相等
你可以使用 == 和 != 比較元組。 這些操作符會依序比較每個元素,因此當兩個元組的所有對應元素相等時,兩個元組是相等的。
var order1 = (Product: "Widget", Quantity: 5);
var order2 = (Product: "Widget", Quantity: 5);
var order3 = (Product: "Gadget", Quantity: 3);
Console.WriteLine(order1 == order2); // True
Console.WriteLine(order1 == order3); // False
// Element names don't affect equality—only values matter
var named = (X: 1, Y: 2);
var different = (A: 1, B: 2);
Console.WriteLine(named == different); // True
元組等式使用每個元素類型上定義的 == 運算子,這意味著對於字串、數字及定義了 == 的其他類型,比較是正確的。 元素名稱不影響等值——只有數值和位置才重要。
非破壞性突變 with
此 with 表達式會建立一個元組的複製品,並改變一個或多個元素,保留原始元素不變:
var original = (Name: "Widget", Price: 19.99m, InStock: true);
var discounted = original with { Price = 14.99m };
Console.WriteLine($"Original: {original.Name} at {original.Price:C}");
Console.WriteLine($"Discounted: {discounted.Name} at {discounted.Price:C}");
// Output:
// Original: Widget at $19.99
// Discounted: Widget at $14.99
這個模式在你想要現有元組的變體而不修改原始元組時非常有用。 這個 with 表達式在元組和 記錄上的運作方式相同。
字典和查找操作中的元組
當你需要將鍵與多個資料片段關聯時,元組是方便的字典值:
var sizeChart = new Dictionary<string, (int Min, int Max)>
{
["Small"] = (0, 50),
["Medium"] = (51, 100),
["Large"] = (101, 200)
};
if (sizeChart.TryGetValue("Medium", out var range))
{
Console.WriteLine($"Medium: {range.Min}–{range.Max}");
}
// Output: Medium: 51–100
元組也可以當作字典 鍵,提供一個複合鍵,無需定義自訂型別。 由於元組實作結構等同,查找會根據所有元素的合併值相匹配:
var grid = new Dictionary<(int Row, int Column), string>
{
[(0, 0)] = "Origin",
[(1, 3)] = "Sensor A",
[(2, 5)] = "Sensor B"
};
var target = (Row: 1, Column: 3);
if (grid.TryGetValue(target, out var label))
{
Console.WriteLine($"({target.Row}, {target.Column}): {label}");
}
// Output: (1, 3): Sensor A
此模式避免了為簡單的多鍵查詢或鍵到多值映射而需要獨立類別。
元組 vs. 匿名型別
當你需要輕量級無名資料結構時,元組是首選。 匿名型別仍可用於表達式樹情境及需要參考型別的程式碼,但元組提供更佳的效能、解構支援及更靈活的語法。 關於匿名型態的更多資訊,請參見 「匿名型別與元組型別的選擇」。
另請參閱
- 有關完整語法細節的元組類型(C# 參考)
- 為使用者定義方法
Deconstruct - 捨棄
- 記錄