基本宣告
public struct Book
{
public int Price;
public string Title;
public string Author;
}
class Test
{
static void Main()
{
Book bk = new Book(); // 不用 new 也可:Book bk;
bk.Price = 23;
bk.Title = "書名";
bk.Author = "作者";
Console.WriteLine($"{bk.Title} {bk.Author} {bk.Price}");
}
}
結構 vs 類別:核心差異對照
| 特性 | 結構 | 類別 |
| 型別分類 | 值型別 | 參考型別 |
| 記憶體配置 | Stack(區域變數) / Inline 在物件內 | Heap |
| 指派行為 | 複製整個值 | 複製參考(指向同一物件) |
| 預設值 | 所有欄位為 0/null/false | null |
| 繼承 | 不可繼承其他結構/類別,不可為基底類別 | 單一繼承,可為基底 |
| 介面 | 可實作介面 | 可實作介面 |
| 建構函式 | 不可定義無參建構函式,需帶參數 | 可定義無參建構函式 |
| 解構函式 | 不可定義 | 可定義 |
| 欄位初始化 | 不可在宣告處初始化(除非 const/static) | 可在宣告處初始化 |
| Null 能力 | 可用 Nullable<T> (T?) 包裝 | 原生可為 null |
| Boxing/Unboxing | 轉為 object/介面時發生 | 天生是參考型別 |
值型別複製語意實測
struct Point { public int X, Y; }
var p1 = new Point { X = 1, Y = 2 };
var p2 = p1; // 複製值
p2.X = 10;
Console.WriteLine(p1.X); // 1 (未改變)
Console.WriteLine(p2.X); // 10
⚠️ 常見陷阱與避坑指南
- 可變結構體是魔鬼:方法修改結構體欄位實際修改的是副本。
✅ 設計為不可變:欄位 readonly、屬性唯讀、方法回傳新實例。
- Boxing 效能陷阱:
object o = myStruct; → Heap 配置 + 複製。頻繁裝箱導致 GC 壓力。
✅ 避免在熱路徑將結構體當 object/介面使用;泛型集合 List<T> 避免裝箱。
- 預設值陷阱:
new MyStruct() 與 default(MyStruct) 相同,所有欄位歸零。若 0 非合法狀態(如 Id=0),需額外驗證或用類別。
- 大小限制:建議 < 16 bytes(4 個 int / 2 個 double)。超過會因頻繁複製拖慢效能,改用類別。
- 避免在介面變數中儲存結構體:
IEnumerable<Point> 會導致逐項裝箱。
適用場景
- 小型、不可變、邏輯上為「值」的資料:座標點、複數、日期時間區間、錢幣金額、RGB 色彩。
- 高效能陣列:
Point[] 連續記憶體、無參考開銷、Cache 友善。
- 互通性:P/Invoke、硬體緩衝區、檔案格式對應的固定大小資料結構。
現代 C# 進階:readonly struct / ref struct (C# 7.2+)
// 編譯器強制不可變,避免防禦性複製
public readonly struct ImmutablePoint
{
public int X { get; }
public int Y { get; }
public ImmutablePoint(int x, int y) { X = x; Y = y; }
}
// 僅能存在於 Stack,不能逃逸到 Heap(不可用於 async/迭代器/Boxing)
public ref struct StackOnlySpan
{
public Span<byte> Data;
}