CodeGym /課程 /C# SELF /從函式返回多個值:out 參數與 tuple

從函式返回多個值:out 參數與 tuple

C# SELF
等級 11 , 課堂 3
開放

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);
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION