在學習 C# 參數傳遞時,書籍上關於 ref 關鍵字的說明通常很直觀:加上 ref 後,方法內部對參數的修改會影響外部變數的值。然而,實際開發中經常會遇到一個令人困惑的現象——陣列(以及其他參考型別)在不加 ref 的情況下,其元素值也能被修改。本文將深入剖析這背後的機制,釐清「傳值」與「傳引用」的真正含義。
先回顧經典範例,說明 ref 對實值型別(如 string、int)的影響:
public static void ValueParam(string str)
{
str = "251"; // 嘗試修改參數指向
}
public static void RefParam(ref string str)
{
str = "250"; // 修改參數指向,會影響外部變數
}
public static void Main()
{
string str = "249";
ValueParam(str);
Console.WriteLine("Value Param: " + str); // 輸出:249
RefParam(ref str);
Console.WriteLine("Ref Param: " + str); // 輸出:250
}
結果解析:string 是不可變的參考型別,但預設以「傳值」方式傳遞(傳遞的是參考的副本)。在 ValueParam 中重新賦值只是改變了區域變數指向的物件,不影響外部;而 RefParam 加上 ref 後,直接操作外部變數的儲存位置,因此能修改其指向。
現在看看陣列參數:
public void ChangeValue(int[] arr)
{
for (int i = 0; i < arr.Length; i++)
arr[i] = 0; // 修改陣列「元素」
}
public static void Main()
{
int[] arr = { 3, 4, 5, 7, 2, 6, 1 };
// 輸出原始值...
ChangeValue(arr); // 未使用 ref
// 輸出修改後的值:全變成 0
}
現象:陣列元素被歸零,雖然沒加 ref。
這並非例外,而是 C# 參數傳遞機制的本質體現:
arr[i] = 0、list.Add()、obj.Property = value):透過副本存取同一物件,**會影響外部可見的物件內容**。arr = new int[5]、str = "new"):只是讓區域參數指向新物件,**不影響外部變數**。ref 的作用是:**讓參數成為外部變數的別名**,任何對參數指向的修改(重新賦值)都會直接反映到外部變數。
| 操作類型 | 實值型別 (int, struct) | 參考型別 (class, array, string) | 加上 ref 後 |
|---|---|---|---|
| 修改內部資料/欄位/元素 | 不影響外部 | 影響外部 | 影響外部 |
| 重新賦值 (指向新物件/新值) | 不影響外部 | 不影響外部 | 影響外部 |
ref 或 out。理解「傳遞參考的副本」與「傳遞變數的別名」的區別,是掌握 C# 參數傳遞的關鍵。陣列元素可被修改並非特例,而是參考型別語意的自然結果。下次看到「沒加 ref 卻改了值」,請記住:你改的是「物件內容」,不是「變數指向」。