CodeGym /課程 /C# SELF /參數修飾詞 ( ref

參數修飾詞 ( refoutin)

C# SELF
等級 12 , 課堂 4
開放

1. 前言

在 C# 裡,預設所有方法參數都是值傳遞,也就是說傳進去的是複製品,不是原本的變數。

可是如果你想要在方法裡面改外面的變數怎麼辦?或者想要一次從方法回傳好幾個值?又或者你要傳一個很大的 struct 進去,但又不想複製整個「沒有把手的行李箱」,只想省記憶體傳個參考就好?

這些情境都跟參數修飾詞有關。它們讓你可以掌控資料在呼叫端跟方法之間的「傳遞路線」。

修飾詞種類

修飾詞 資料傳遞 呼叫前要初始化嗎? 裡面可以讀嗎? 裡面可以寫嗎? 常見使用場景
ref 雙向 可以 可以 傳變數進去讀或改
out 只往外 不用 不行(賦值前) 一定要(強制) 從方法回傳多個值
in 只往內 可以 不行 傳大 struct 進去「唯讀」又省記憶體

別擔心,等等我們會一個一個看,其實比你想像的簡單多了。

2. ref 修飾詞:傳參考,能讀也能寫

想像一下:你拿一杯咖啡給朋友,跟他說:「這杯你可以喝、可以加熱、甚至想加什麼都行。」朋友可以對這杯咖啡為所欲為,最後剩下什麼(或沒剩什麼)都還你。這就是 ref 的概念。


void DoSomething(ref int x)
{
    x = x + 10; // 改變傳進來的參數
}
        
ref 參數的方法

要用 ref 傳參數,宣告跟呼叫都要加這個修飾詞:

int myNumber = 5;
DoSomething(ref myNumber);
Console.WriteLine(myNumber); // 會印出 15

一定要是變數,不能傳運算式。這樣會出錯:

DoSomething(ref 10);		// 這裡一定要是變數!
  • 呼叫前:變數一定要初始化(要有值)。
  • 方法內:可以讀也可以改。
  • 呼叫後:改動會保留。

範例:交換兩個變數的值

有時候要交換兩個變數的值。但有個重點:C# 的值型別(像 int)預設是值傳遞。也就是說傳進方法的是值的複製品,不是原本的變數。要讓方法能改呼叫端的變數,就要用 ref

沒用 ref 的範例 — 不會動:

// 嘗試交換值 — 不會成功
void Swap(int a, int b)
{
    int temp = a;
    a = b;
    b = temp;
}

int x = 10;
int y = 20;
Swap(x, y); // 傳的是值的複製品
Console.WriteLine($"{x}, {y}"); // → 10, 20 — 完全沒變!

Swap 函數裡 ab 只是 xy 的複製品。我們只改到複製品,原本的沒動到。

ref 的範例 — 真的交換了


// 用 ref 交換變數的值
void Swap(ref int a, ref int b)
{
    int temp = a;
    a = b;
    b = temp;
}

int x = 10;
int y = 20;
Swap(ref x, ref y); // 傳的是變數的參考
Console.WriteLine($"{x}, {y}"); // → 20, 10 — 真的換了
        

ref 讓你傳的是變數的參考,不是值。
所以 Swap 直接操作 xy,內容就被換掉了。

什麼時候該用 ref

  • 你想直接改傳進來的變數(像加一、換掉)。
  • 不想用 return 回傳新值,而是直接改參數。
  • 不要用來「塞一堆值」出去 — 這種情境用別的修飾詞比較好。

3. out 修飾詞:只往外傳

換個想像:你帶著空杯子去找朋友,跟他說:「幫我裝點東西,咖啡茶都行!」這時你朋友一定要幫你裝點什麼,不然編譯器會發飆。

out 就是讓方法能回傳多個值,不用靠 return。這種參數一定要在方法裡被賦值。


void GetCoordinates(out int x, out int y)
{
    x = 5;
    y = 10; // 沒這行會錯!
}
        
out 參數的方法
  • 呼叫前:變數可以不用初始化,甚至可以直接在呼叫時寫 out int x
  • 方法內:一定要賦值,不然編譯器會罷工。
  • 呼叫後:變數有新值。

範例:一次回傳兩個值

void ParseNameAndAge(string input, out string name, out int age)
{
    string[] parts = input.Split(',');	// 把字串切成陣列 - 以 ',' 分割
    name = parts[0];
    age = int.Parse(parts[1]);
}

string userInput = "Иван,25";
ParseNameAndAge(userInput, out string userName, out int userAge);
Console.WriteLine($"{userName} — {userAge} 歲");

什麼時候該用 out

  • 你想從方法回傳多個值(像上面把字串拆開)。
  • 回傳型別不一定(像嘗試把字串轉成數字)。

.NET 經典範例: int.TryParse(string, out int) — 超常用!

string input = "123";
if (int.TryParse(input, out int parsedNumber))
{
    Console.WriteLine($"轉換成功: {parsedNumber}");
}
else
{
    Console.WriteLine("轉換失敗!");
}

int.TryParse 會回傳 true 代表轉換成功,false 代表失敗。數字本身是透過 out 回傳的。

refout 常見錯誤跟趣事

  • 忘記初始化 ref 變數 — 編譯器會生氣。
  • 沒給 out 變數賦值 — 編譯器更生氣。
  • 呼叫時忘了加修飾詞:寫 DoSomething(x) 而不是 DoSomething(ref x)
  • refout 不能用常數或字面值!只能用變數。只有變數才有記憶體位址可以傳參考。

4. in 修飾詞:唯讀參考

有時候你要把一個很大很複雜的物件傳進方法。通常這種物件會用參考傳,但這樣方法就能隨便改它 — 因為你給了它參考。

理論上你可以複製一份再傳進去,這樣原本的不會被改。但如果物件很大,這樣會白白複製一堆資料。最省的方法是直接傳參考,然後請編譯器幫你確保沒人會改它。

in — 新的修飾詞,讓你可以把struct 用參考傳進去,但只能讀不能改。就像「玻璃櫃裡的寶劍」:只能看,不能拿。


void PrintPoint(in Point pt)
{
    pt.X = 5; // 錯誤! 只能讀
    Console.WriteLine($"點: {pt.X}, {pt.Y}");  //這樣可以
}
        
in 參數的方法(唯讀)
  • 呼叫前:變數要初始化。
  • 方法內:只能讀,不能改。
  • 省記憶體:struct 不會被複製,是傳參考。

很適合用在大 struct 陣列,避免多餘的複製。不過對 class(參考型別)來說 in 沒什麼特別效果。

範例:傳大 struct 用參考,省複製

struct BigData
{
    public int A, B, C, D, E;
}

void PrintBigData(in BigData data)
{
    data.A = 10; // 不行 - 只能讀!
    Console.WriteLine(data.A + data.B + data.C + data.D + data.E);
}

BigData myData = new BigData { A = 10, B = 20, C = 30, D = 40, E = 50 };
PrintBigData(in myData);

5. 常見問題:

ref/out/in 可以用在陣列跟參考型別嗎?
可以!但要記得:陣列本身就是參考型別。用 ref 傳陣列時,你可以改變參考本身。通常這種修飾詞比較常用在 struct。

ref 跟 out 有什麼差別?不是都能拿到值?
ref 變數一定要先初始化才能用。out 不用,但在方法裡一定要賦值一次。

如果我用 ref/out/in 傳字面值(像 5)會怎樣?
編譯器馬上報錯。只能用變數!

ref/out/in 可以用幾個參數?
幾個都行,沒上限!但為了程式可讀性,建議不要超過兩三個,不然可以考慮用 tuple、struct 或 class。

1
問卷/小測驗
類別與物件,等級 12,課堂 4
未開放
類別與物件
繼承入門
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION