1. 前言
想像一下,如果我們有個函式,根據傳進來的使用者(比如名字)要一次回傳年齡、註冊日期還有啟用狀態 flag。在 C 跟早期 C#,大家都覺得函式只能回傳一個值很正常。那如果要回傳好幾個值怎麼辦?這個問題就讓大家想出各種做法,每種都有自己的優缺點。
在前面的課程我們已經碰過 tuple。現在要來學 out 參數,然後把這兩種方式拿來比較一下。
最常見的幾種做法:
- 用 out 參數。
- 回傳一個有需要欄位的物件(匿名型別或自訂 class)。
- 回傳 tuple(ValueTuple)。
這堂課我們會仔細看其中兩種方式——out 參數跟 tuple,然後來個正面對決,看看哪個方式在多值回傳時比較有優勢。
2. out 參數:來自過去的招式
看到這種函式的時候:
void GetUserInfo(string userName, out int age, out DateTime registrationDate, out bool isActive)
{
// 這裡做計算
age = 42;
registrationDate = new DateTime(2010, 1, 1);
isActive = true;
}
你可能會想問:這到底是函式還是洗車場分店?一次回傳一堆東西,還都不是用 return 傳回去的!
怎麼運作
呼叫端要先宣告好變數,函式才會幫你填值:
int age;
DateTime reg;
bool isActive;
GetUserInfo("Bob", out age, out reg, out isActive);
// 現在所有變數都有值了
Console.WriteLine($"{age}, {reg}, {isActive}");
out 方式的缺點
- 比較難讀: 方法簽名會變超長,參數名字也不一定能馬上看懂是什麼意思。
- 不方便串接: 這種方法沒辦法直接串接(像是把結果直接傳給另一個方法)。
- 會改到傳進來的變數: 方法一定要去改外面的變數,如果忘記加 out,編譯器就會報錯。
- 初始化有點麻煩: 編譯器會逼你先宣告外部變數,就算你只想用一次結果也一樣。
- 大方法裡可讀性差: 如果 out 參數一堆,很容易搞混哪個是什麼。
這時候 tuple 就開始閃閃發光啦~
3. Tuple——進化版做法
來看個例子比較一下
用 tuple:
public (int Age, DateTime RegistrationDate, bool IsActive) GetUserInfo(string userName)
{
// 模擬查資料庫
return (42, new DateTime(2010, 1, 1), true);
}
怎麼用:
// 一次拿到所有值,還能直接命名
var (age, regDate, isActive) = GetUserInfo("Bob");
Console.WriteLine($"{age}, {regDate}, {isActive}");
不用多宣告變數、沒 out,超級直觀好讀!
比較表:out vs tuple
| 比較項目 | Out 參數 | Tuple(ValueTuple) |
|---|---|---|
| 簽名 | 很長,out 參數都在參數列 | 很精簡,全部一次回傳 |
| 用法 | 要先宣告變數再呼叫方法 | 可以直接解構 |
| 可讀性 | out 參數多時很容易看不懂 | 元素名字一看就懂 |
| 好組合嗎? | 不行 | 可以,還能巢狀或傳來傳去 |
Tuple VS. out——底層發生什麼事
用 out 參數時,函式其實是在操作自己「地盤」外的記憶體:簡單說,就是去改外面已經宣告好的變數。這樣要小心,因為很容易不小心把變數搞亂(還好編譯器會強制你一定要 assign!)。
Tuple 則是資料結構,函式會把它組好、初始化好再回傳出去。這樣一來,所有東西都包在一起,不會在路上掉東掉西,也不會忘記。
讀跟維護程式碼
你一定懂——隔一週再看別人寫的 code,最想看到的是:
(var age, var city, var isActive) = GetUserInfo("Anna");
而不是
int age;
string city;
bool isActive;
GetUserInfo("Anna", out age, out city, out isActive);
Tuple 讓方法簽名更乾淨,結果也一目了然。
4. 哪些情境 tuple 特別好用
1. 要回傳結果跟錯誤訊息時
public (bool Success, string ErrorMessage) TryProcess(string data)
{
if (string.IsNullOrEmpty(data))
return (false, "沒有資料");
// 處理資料...
return (true, "");
}
var (ok, error) = TryProcess(input);
if (!ok)
Console.WriteLine($"錯誤:{error}");
2. 回傳多個搜尋結果的函式
public (User? FoundUser, int Index) FindUserByName(User[] users, string name)
{
for (int i = 0; i < users.Length; i++)
{
if (users[i].Name == name)
return (users[i], i);
}
return (null, -1);
}
3. 「成對」值:算最小值跟最大值
public (int min, int max) FindMinMax(int[] numbers)
{
int min = numbers[0], max = numbers[0];
foreach (var n in numbers)
{
if (n < min) min = n;
if (n > max) max = n;
}
return (min, max);
}
var (minValue, maxValue) = FindMinMax(arr);
GO TO FULL VERSION