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

《Effective C#》精華:值類型與引用類型核心差異與編碼陷阱

     編輯:關於C#

核心分類

分類關鍵字/示例內存分配賦值語義參數傳遞默認
值類型`struct`、`int`/`double`/`bool`、`DateTime`、`enum`**棧** (或內嵌於引用類型對象內部)**複製** 創建獨立副本**按值傳遞** (複製副本)
引用類型`class`、`string`、數組、`delegate`、介面**托管堆** (GC 管理)**複製引用** 指向同一對象**按引用傳遞** (複製引用)

關鍵差異對比

特性值類型引用類型
內存效率高 (無 GC 壓力、無指針間接訪問)低 (堆分配、GC 掃描、間接訪問)
生命週期作用域結束立即銷毀 (棧展開)不可達時由 GC 非確定性回收
賦值 `a = b`字段逐位複製,互不影響引用複製,`a` 與 `b` 指向同一對象
方法參數修改修改副本,不影響實參修改對象內容,**影響實參**
繼承**不可繼承** (隱式 `sealed`),可實現介面支持繼承、多態
默認值零初始化 (0/false/null 字段)`null`
裝箱/拆箱轉 `object`/介面時發生 (性能損耗)無

陷阱 1:值類型參數無法修改實參

// ❌ 錯誤:修改的是副本
public void Increment(int i) { i++; }

int x = 10;
Increment(x);
Console.WriteLine(x); // 10 (未變)

// ✅ 正確:用 ref/out 傳遞引用
public void Increment(ref int i) { i++; }
Increment(ref x);
Console.WriteLine(x); // 11
💡 `ref`:要求實參已初始化;`out`:實參無需初始化,方法**必須**賦值。二者 IL 略有不同,語義上 `out` 更明確「輸出參數」。

陷阱 2:引用類型參數意外修改原對象

public void AddValue(MyData data) {
    data.Count += 15; // 修改了原對象!
}

var d = new MyData { Count = 10 };
AddValue(d);
Console.WriteLine(d.Count); // 25 (原對象被改)

解決方案:防禦性複製 (Clone)

// 1. 實現 ICloneable
public class MyData : ICloneable {
    public int Count { get; set; }
    public object Clone() => new MyData { Count = this.Count }; // 淺拷貝
}

// 2. 調用端傳遞副本
AddValue((MyData)d.Clone()); // 原對象安全

// 3. 或方法內部複製 (API 設計者視角)
public void AddValueSafe(MyData data) {
    data = (MyData)data.Clone(); // 參數重新綁定副本
    data.Count += 15; // 只改副本
}

⚠️ 深拷貝難題

對象圖包含嵌套引用類型時,`Clone` 需遞歸複製所有層級 (深拷貝):**極易出錯、性能差、維護難**。

  • 序列化往返 (`BinaryFormatter`/`JsonSerializer`) 可實現通用深拷貝,但慢。
  • **設計建議**:盡量**不可變類型** (唯讀屬性、構造函數注入),從根源消除修改風險。

如何選擇:`struct` 還是 `class`?

《Effective C#》建議:**滿足以下全部條件才用 `struct`**,否則用 `class`:

  1. **主要用於數據存儲** (少行為、多屬性)。
  2. **僅通過屬性訪問數據** (封裝良好)。
  3. **不會有子類型** (值類型不可繼承)。
  4. **不會以多態方式處理** (無需作為基類/介面引用傳遞)。

典型 `struct` 適用場景

  • 座標點 (`Point`)、矩形 (`Rectangle`)、顏色 (`Color`)、日期時間 (`DateTime`)、`decimal`、自定義數值結構。
  • 高頻創建、銷毀、數組存儲大量實例 (如粒子系統、遊戲實體屬性)。

典型 `class` 適用場景

  • 業務實體 (`Customer`、`Order`)、服務類、控制器、ViewModel、任何需繼承/多態/介面的類型。

進階:`readonly struct` / `ref struct` (C# 7.2+)

  • `readonly struct`:編譯器強制所有字段 `readonly`,防止防禦性複製,性能更優。
  • `ref struct`:**只能棧分配** (如 `Span`),不可裝箱、不可作為類字段、不可在 `async`/`yield` 中使用,極高性能場景。

最佳實踐清單

  • ✅ **默認選 `class`**,除非明確滿足 `struct` 四條件。
  • ✅ **值類型參數需修改實參** → `ref`/`out` (或返回新值)。
  • ✅ **引用類型參數防修改** → 不可變類型 / 防禦性複製 / `readonly` 包裝器。
  • ✅ **避免大型 `struct`** (>16 bytes) 頻繁按值傳遞 (複製開銷) → 改 `class` 或 `ref`/`in` 參數 (C# 7.2 `in` 參數)。
  • ✅ **實現 `IEquatable`** 避免值類型裝箱比較。
  • ✅ **覆寫 `Equals`/`GetHashCode`** (值類型語義相等) / 實現 `IEquatable`。
  • ✅ **`string` 是引用類型但具值類型語義** (不可變、== 比較內容) —— 特例請記住。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved