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

C# 陣列做參數傳遞的陷阱與防禦性設計指南

     編輯:關於C語言

C# 參數傳遞機制常讓開發者混淆:「傳遞參考型別」≠「ref 傳遞」。陣列為參考型別,預設以值傳遞參考(即複製陣列參考),導致被呼叫端能修改陣列元素內容,卻無法替換整個陣列實例。本文深入剖析此行為,並提供三層防禦策略:readonly、唯讀介面、防禦性複製。

核心概念釐清

分類傳遞機制能否修改元素能否替換整個陣列
陣列(參考型別)
預設(值傳參考)
複製參考是否(只影響區域變數)
陣列 + ref傳遞參考的參考是是
List<T> 預設複製參考是否
結構體(值型別)預設複製實例否(修改副本)不適用

實證程式碼

void ModifyArray(int[] arr)
{
    arr[0] = 999;           // 影響原陣列元素
    arr = new int[] { 1, 2 }; // 僅改變區域參考,呼叫端不受影響
}

void ReplaceArray(ref int[] arr)
{
    arr = new int[] { 1, 2 }; // 呼叫端陣列變數指向新實例
}

int[] a = { 10, 20, 30 };
ModifyArray(a);
Console.WriteLine(a[0]); // 999 ← 元素被改了
ReplaceArray(ref a);
Console.WriteLine(a[0]); // 1   ← 整個陣列被換了

防禦層級 1:唯讀包裝(.NET 4.5+ 推薦)

宣告參數為 IReadOnlyList<T> 或 IReadOnlyCollection<T>,編譯期即阻擋修改嘗試:

// 呼叫端
int[] data = { 1, 2, 3 };
Process(data); // 隱含轉換 int[] → IReadOnlyList

// 被呼叫端
void Process(IReadOnlyList items)
{
    // items[0] = 9; // CS0200: 唯讀索引子不可指派
    foreach (var x in items) Console.WriteLine(x); // 唯讀遍歷安全
}

優點:零額外記憶體、零複製、意圖明確、支援陣列與 List<T> 多型。
缺點:無法阻止惡意轉型 (items as int[])?[0] = 9,但屬「自找麻煩」層級。

防禦層級 2:防禦性複製

若必須確保內部邏輯不受外部後續修改影響,或需回傳不可變快照:

void Process(int[] input)
{
    int[] snapshot = (int[])input.Clone(); // 淺層複製,值型別元素安全
    // 對 snapshot 操作,不影響原陣列
}

int[] GetData()
{
    return _internalArray.ToArray(); // 回傳副本,保護內部狀態
}

陣列為一維且元素為值型別時,Clone()/ToArray() 即為深層複製;元素為參考型別時,僅複製參考,仍需考慮元素不可變性。

防禦層級 3:不可變集合

.NET 6+ / NuGet System.Collections.Immutable:

using System.Collections.Immutable;

void Process(ImmutableArray items) // 結構體,傳值語意,絕對不可變
{
    // items[0] = 9; // 編譯錯誤
    var newItems = items.Add(4); // 產生新實例,原不變
}

ImmutableArray arr = ImmutableArray.Create(1, 2, 3);
Process(arr);

設計原則速查表

  1. 預設參數型別用最寬鬆的唯讀介面:IReadOnlyList<T> > IEnumerable<T> > T[] / List<T>。
  2. 唯讀介面無法滿足(需索引、計數、切片)時,改用 ReadOnlySpan<T> (.NET Core 2.1+),零配置、棧上、高效能。
  3. 內部儲存陣列:永不直接暴露 private T[] _arr,提供 IReadOnlyList<T> Items => _arr 或 _arr.ToArray() 副本。
  4. 避免 ref/out 陣列:除非極少數高效能互換場景,否則破壞封裝、增加認知負擔。
  5. 文件化副作用:XML 註解標註 `不會被修改` 或 `若輸入為 null`。

常見誤區

  • 「加 readonly 修飾陣列欄位就安全了」:readonly int[] _arr 只防止替換陣列實例,_arr[0] = 9 依然合法。
  • 「params 陣列就不會被改」:params 只是語法糖,本質仍是陣列參考傳遞。
  • 「Span<T> 就能防禦」:Span<T> 本身可修改元素,需用 ReadOnlySpan<T>。

掌握「參考型別值傳遞 = 複製參考」這一核心規則,配合 IReadOnlyList<T> / ReadOnlySpan<T> / ImmutableArray<T> 現代工具箱,即能在編譯期與執行期雙重保障陣列參數的安全傳遞,消除難以重現的「莫名其妙資料被改」Bug。

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