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

C# 結構完整指南:值型別語意、與類別差異、效能考量與最佳實踐

     編輯:C# 入門

C# 結構完整指南:值型別語意、與類別差異、效能考量與最佳實踐

結構 是 C# 的實值型別建構單元,適合表示小型、不可變、資料導向的型別。結構儲存在堆疊(或內含物件內部),採複製語意,無繼承、無解構子、無參考身分。正確使用結構能顯著提升效能、減少 GC 壓力;濫用則導致過度複製、封箱開銷。

結構 vs 類別:核心差異對照

特性結構類別
型別分類實值型別參考型別
記憶體配置堆疊 / 內嵌堆積
指派行為複製值複製參考
繼承不可繼承類別/結構,可實作介面單一繼承,可實作介面
預設值所有欄位為 0/nullnull
建構子不可定義無參數建構子 (C# 10 前)可定義任意建構子
解構子不支援支援
Null不可為 null (除非用 Nullable<T>)可為 null
虛擬方法不可 virtual/abstract/protected支援
封箱/解箱轉為 object/介面時發生天然為參考

定義結構

public readonly struct Point3D { // readonly struct (C# 7.2+) 確保不可變
    public double X { get; }
    public double Y { get; }
    public double Z { get; }
    
    public Point3D(double x, double y, double z) {
        X = x; Y = y; Z = z;
    }
    
    public double DistanceFromOrigin => Math.Sqrt(X*X + Y*Y + Z*Z);
    
    public override string ToString() => $"({X}, {Y}, {Z})";
    
    // 運算子多載
    public static Point3D operator +(Point3D a, Point3D b) => 
        new Point3D(a.X + b.X, a.Y + b.Y, a.Z + b.Z);
}

結構的特殊規則

無參數建構子

C# 10 前:結構不能宣告無參數建構子,預設建構子將所有欄位歸零。C# 10+ 允許自訂無參數建構子,但必須初始化所有欄位:

public struct Color {
    public byte R, G, B, A;
    public Color() { R = G = B = 0; A = 255; } // C# 10+ 合法
}

欄位初始化限制

實例欄位不可在宣告處初始化(C# 10 前):

public struct Bad {
    // public int Value = 5; // 編譯錯誤
    public int Value; // 必須在建構子初始化
}

readonly struct

C# 7.2+ readonly struct 強制所有實例成員為 readonly,編譯器保證不可變性,避免防禦性複製:

public readonly struct ImmutablePoint {
    public int X { get; }
    public int Y { get; }
    public ImmutablePoint(int x, int y) { X = x; Y = y; }
    // 編譯器檢查:不可有 set 存取子、不可修改欄位
}

ref struct

C# 7.2+ ref struct 僅能分配在堆疊,不可用於堆積(陣列元素、類別欄位、封箱、非同步方法、迭代器):

public ref struct SpanWrapper {
    public Span Data; // 僅限堆疊
    // 不可作為類別欄位、陣列元素、封箱
}

典型用途:Span<T>、ReadOnlySpan<T>、ref struct 狀態機。

效能考量:何時用結構?

適用場景

  • 小型資料結構(< 16 bytes 為經驗值):座標、顏色、日期時間、矩陣。
  • 大量陣列/集合:Point[] 連續記憶體、無額外參考開銷、Cache 友善。
  • 高頻呼叫、熱路徑:避免配置/GC,減少間接存取。
  • 不可變值物件:Domain-Driven Design 的 Value Object。

不適用場景

  • 大型結構(> 16-32 bytes):參數傳遞、回傳、複製開銷大。
  • 頻繁封箱:存入 List<object>、介面變數、非泛型集合。
  • 需繼承/多型:結構不支援繼承。
  • 需參考語意:共享可變狀態、實體身分。

實戰範例:高效能向量運算

public readonly struct Vector3 {
    public readonly float X, Y, Z;
    public Vector3(float x, float y, float z) { X = x; Y = y; Z = z; }
    
    public float Length => MathF.Sqrt(X*X + Y*Y + Z*Z);
    public float LengthSquared => X*X + Y*Y + Z*Z;
    
    public static Vector3 operator +(Vector3 a, Vector3 b) => 
        new Vector3(a.X + b.X, a.Y + b.Y, a.Z + b.Z);
    public static Vector3 operator -(Vector3 a, Vector3 b) => 
        new Vector3(a.X - b.X, a.Y - b.Y, a.Z - b.Z);
    public static Vector3 operator *(Vector3 v, float s) => 
        new Vector3(v.X * s, v.Y * s, v.Z * s);
    public static float Dot(Vector3 a, Vector3 b) => a.X*b.X + a.Y*b.Y + a.Z*b.Z;
    public static Vector3 Cross(Vector3 a, Vector3 b) => new Vector3(
        a.Y*b.Z - a.Z*b.Y,
        a.Z*b.X - a.X*b.Z,
        a.X*b.Y - a.Y*b.X
    );
}

// 使用:零配置、零 GC
var particles = new Vector3[1_000_000];
for (int i = 0; i < particles.Length; i++) {
    particles[i] = new Vector3(random.NextSingle(), random.NextSingle(), random.NextSingle());
}
// 批次運算:極佳快取局部性
for (int i = 0; i < particles.Length; i++) {
    particles[i] = particles[i] * 0.99f + new Vector3(0, -9.8f, 0) * 0.016f;
}

常見陷阱與解決方案

防禦性複製

呼叫非 readonly 結構的 readonly 欄位/屬性時,編譯器會建立副本防止意外修改:

public struct Counter { public int Value; public void Increment() => Value++; }
public class Holder { public readonly Counter Counter = new Counter(); }

var h = new Holder();
h.Counter.Increment(); // 編譯器警告:在 readonly 欄位上呼叫非 readonly 方法,會複製
Console.WriteLine(h.Counter.Value); // 0 (修改的是副本)

// 解決:用 readonly struct 或移除 readonly 欄位修飾詞

避免在迴圈中封箱

// 錯誤:每次迴圈封箱
foreach (object o in myStructs) { Console.WriteLine(o.ToString()); }

// 正確:泛型避免封箱
foreach (var s in myStructs) { Console.WriteLine(s.ToString()); }

最佳實踐

  • 預設用 class,只有滿足「小、不可變、值語意、高頻」才考慮 struct。
  • 標記 readonly struct:確保不可變、避免防禦性複製、意圖明確。
  • 實作 IEquatable<T>:避免封箱的值相等比較。
  • 覆寫 Equals/GetHashCode/ToString:提供正確值語意。
  • 避免可變結構:可變結構極易產生微妙 Bug(複製語意導致修改失效)。
  • 大小控制:單一結構不超過 4-8 個基本型別欄位。

常見問題 FAQ

Q: 結構可以為 null 嗎?

A: 本身不可,但可用 Nullable<T> / T? 包裝:int? x = null; DateTime? d = null;。

Q: 結構實作介面會封箱嗎?

A: 會。透過介面型別存取結構實例會發生封箱。泛型約束 where T : IInterface 可避免封箱。

Q: C# 10 結構無參數建構子有什麼用?

A: 允許自訂預設值(如 Color() { A=255 }),但new T() 和 default(T) 行為不同:前者呼叫無參數建構子,後者仍歸零。

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