首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> ASP.NET 基礎 >> ASP.NET 基礎

C# 參數修飾詞完整指南:ref、out、params 差異、多載規則與記憶體語意

     編輯:ASP.NET 基礎

快速對照表

修飾詞方向呼叫端初始化方法內必須指派可變參數多載區別
params輸入N/A否是 (陣列)視為陣列參數
ref雙向必須否 (可選)否與 out 不能互相多載
out輸出不需是 (所有路徑)否與 ref 不能互相多載
(無)輸入必須否否與 ref/out 可多載

params:靈活的參數陣列


public static void Print(params object[] args)
{
    Console.WriteLine(string.Join(" ", args.Select(a => a?.ToString() ?? "null")));
}

// 呼叫方式
Print();                          // 空陣列
Print(1, 2, 3);                   // 隱式建立 int[] → object[]
Print("A", "B", "C");             // 字串陣列
Print(new object[] { 1, "x", 3.14 }); // 顯式陣列
int[] nums = { 10, 20 };
Print(nums);                      // 直接傳遞陣列 (無複製)

規則:僅能出現在參數列表最後,每個方法最多一個。

ref:雙向參考傳遞


// 實值型別:交換值
public static void Swap(ref int a, ref int b) 
{ int t = a; a = b; b = t; }

// 參考型別:修改內容 vs 替換參考
public static void Append(ref StringBuilder sb) 
{ sb.Append(" world"); }           // 修改內容,影響外部

public static void Replace(ref StringBuilder sb) 
{ sb = new StringBuilder("new"); } // 替換參考,影響外部變數指向

// 呼叫端必須初始化
StringBuilder sb = new StringBuilder("hello");
Append(ref sb);    // sb 變 "hello world"
Replace(ref sb);   // sb 變 "new"

out:強制輸出參數


// TryParse 模式
public static bool TryGetUser(int id, out User user, out string error)
{
    user = null; error = null; // 編譯器要求所有路徑指派
    if (id <= 0) { error = "Invalid ID"; return false; }
    user = _repo.Get(id);
    return user != null;
}

// 解構語法 (C# 7+)
public static void Decompose(Point p, out int x, out int y)
{
    x = p.X; y = p.Y;
}

// 呼叫
if (TryGetUser(1, out var user, out var err)) { ... }
Decompose(point, out int x, out int y); // 內嵌宣告

多載解析規則


// ✅ 合法多載:參數類型不同
void M(int i) { }
void M(ref int i) { }
void M(out int i) { }
void M(params int[] arr) { }

// ❌ 非法多載:僅 ref/out 差異
void M(ref int i) { }
void M(out int i) { }  // CS0663: 無法定義僅 ref/out 不同的多載

// ⚠️ 呼叫時歧義性
M(1);       // 呼叫 M(int)
M(ref x);   // 呼叫 M(ref int)
M(out y);   // 呼叫 M(out int)

記憶體與語意模型

值傳遞 (預設):
  實值型別 → 複製值 (棧上獨立副本)
  參考型別 → 複製參考 (指向同一堆積物件)

ref 參考傳遞:
  實值型別 → 傳遞變數位址 (方法操作同一棧位置)
  參考型別 → 傳遞參考變數位址 (可改變外部變數指向)

out 輸出參數:
  語意上等同 ref,但編譯器強制:
  1. 呼叫端不需初始化
  2. 方法內所有執行路徑必須指派
  3. 方法內讀取前必須先指派 (視為未初始化)

進階:ref returns、ref locals、in 修飾詞 (C# 7+)


// ref return:回傳參考而非複製值
public ref int FindMax(ref int[] numbers)
{
    int maxIndex = 0;
    for (int i = 1; i < numbers.Length; i++)
        if (numbers[i] > numbers[maxIndex]) maxIndex = i;
    return ref numbers[maxIndex]; // 回傳陣列元素的參考
}

// 使用
int[] arr = { 1, 5, 3 };
ref int max = ref FindMax(ref arr);
max = 10; // 直接修改陣列元素
// arr 變 { 1, 10, 3 }

// in 修飾詞:唯讀參考傳遞 (避免防禦性複製)
public readonly struct BigStruct { public int X, Y, Z; }
public static void Process(in BigStruct data) // 不允許修改 data
{
    // data.X = 1; // 編譯錯誤
    Console.WriteLine(data.X);
}

// ref struct:僅能存在於棧上 (高效能切片)
public ref struct Tokenizer
{
    private ReadOnlySpan<char> _input;
    public Tokenizer(ReadOnlySpan<char> input) => _input = input;
}

最佳實踐建議

  1. 預設不使用 ref/out:傾向回傳 `Tuple`、`ValueTuple`、`Record`、自訂 `Result<T>`
  2. out 專用於 TryXXX 模式:`bool TryParse/TryGet/TryDo(out T result, out string error)`
  3. params 專用於工具/日誌方法:`Log(params string[])`、`Format(params object[])`
  4. 避免 ref 參考型別:極易造成難以追蹤的副作用,除非明確需要「替換呼叫端變數指向」
  5. 大型結構體傳遞用 `in`:`void Process(in BigStruct data)` 避免複製且防禦性修改
  6. 高效能場景用 `ref struct` + `Span<T>`:解析器、緩衝區處理、零配置切片

總結

ref/out/params 是 C# 參數傳遞機制的三大擴展。核心差異在於初始化要求、指派義務、參考方向。現代 C# (7.0+) 引入 ref return、in、ref struct 進一步豐富了參考語意,配合 Span<T> 實現高效能無配置操作。實務中以不可變型別與函數式回傳取代 ref/out,代碼更安全、可測試、易平行化。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved