C# 參數傳遞分為 值傳遞(預設)與 參考傳遞(ref/out/in)。理解「參數本身」與「參數指向的物件」的區別,是避免常見 Bug 的關鍵。
| 模式 | 關鍵字 | 可修改參數本身 | 需初始化 | 適用場景 |
|---|---|---|---|---|
| 值傳遞 | (無) | 否 | 是 | 唯讀輸入 |
| 參考傳遞 | ref | 是 | 是 | 雙向資料流、Swap |
| 輸出參數 | out | 是 | 否(方法內必指派) | 多重回傳值、TryParse 模式 |
| 唯讀參考 | in (C# 7.2+) | 否(編譯器強制) | 是 | 大型結構體避免複製、唯讀傳遞 |
void Square(int x) { x *= x; } // 只影響區域副本
int v = 5;
Square(v);
Console.WriteLine(v); // 5(未變)
void Square(ref int x) { x *= x; }
int v = 5;
Square(ref v);
Console.WriteLine(v); // 25(已變)
bool TryParse(string s, out int result)
{
return int.TryParse(s, out result);
}
TryParse("123", out int n); // n = 123
參考類型變數儲存的是「物件參考」(指標)。
void Modify(List list)
{
list.Add(99); // 影響原物件內容 ✓
list = new List(); // 只改變區域參考,不影響呼叫端 ✗
}
var myList = new List { 1, 2 };
Modify(myList);
Console.WriteLine(myList.Count); // 3(內容被修改)
void Replace(ref List list)
{
list = new List { 100 }; // 呼叫端的變數也會指向新物件
}
var myList = new List { 1, 2 };
Replace(ref myList);
Console.WriteLine(myList[0]); // 100
void Swap(ref int a, ref int b)
{
int t = a; a = b; b = t;
}
void Swap(ref string a, ref string b)
{
string t = a; a = b; b = t;
}
void ChangeArray(int[] arr)
{
arr[0] = 999; // 修改元素:影響原陣列
arr = new int[3]; // 重新指派:不影響原陣列
}
void ChangeArrayRef(ref int[] arr)
{
arr = new int[3]; // 影響原陣列參考
}
// 唯讀參考傳遞,避免大型結構體複製
readonly struct Vector3 { public double X, Y, Z; }
double Magnitude(in Vector3 v) => Math.Sqrt(v.X*v.X + v.Y*v.Y + v.Z*v.Z);
// ref readonly 回傳:避免複製且禁止修改
ref readonly Vector3 GetOrigin() => ref _origin;
// ref struct:只能存在於堆疊,適合高效能 Span-like 型別
ref struct Token { public ReadOnlySpan Text; }
ref:避免不必要的封箱out 或 Tuple:TryGetValue(out var v) 或 (bool, T)in:避免複製成本ref:增加耦合、降低可讀性、難以非同步化ref:除非確實要「換掉呼叫端的物件參考」「參考類型預設就是傳參考」——錯誤。預設是「值傳遞參考」,即複製指標。只有加上
ref才是「參考傳遞參考」。