博客 / 詳情

返回

C#.NET Record Struct 完全解析:語法、語義與最佳實踐

簡介

Record Structs 是一種值類型的記錄(record),結合了 struct 的值語義和 record 的功能(如自動生成相等性比較、不可變性支持)。它們是 C# 9.0 中引入的引用類型 record(默認 class)的擴展,專為性能敏感場景設計,特別是在需要棧分配或避免 GC 壓力的情況下。

核心特性

  • 值類型:存儲在棧上(除非裝箱),避免堆分配,適合小數據結構。
  • 不可變性:默認鼓勵不可變設計(通過 init-only 屬性),但可選擇可變。
  • 值相等性:自動實現基於內容的相等性比較(== 和 Equals)。
  • 自動 ToString:生成人類可讀的字符串表示。
  • 解構支持:自動提供 Deconstruct 方法,方便模式匹配和解構。
  • With 表達式:支持非破壞性變異(創建新實例)。
  • 繼承支持:支持繼承其他 record structs(但不能繼承 classrecord class)。

基本語法

// 最簡單的記錄結構聲明
public record struct Point(int X, int Y);

// 等價於傳統的結構體聲明(但功能更強大)
public struct Point
{
    public int X { get; init; }
    public int Y { get; init; }
    
    public Point(int x, int y)
    {
        X = x;
        Y = y;
    }
    
    // 自動生成的 Equals、GetHashCode、ToString 等方法
}

可變性控制

// 不可變記錄結構 (推薦)
public readonly record struct ImmutablePoint(int X, int Y);

// 可變記錄結構
public record struct MutablePoint
{
    public int X { get; set; }
    public int Y { get; set; }
}

// 混合可變性
public record struct MixedPoint(int X, int Y) // X 和 Y 默認只讀
{
    public int Z { get; set; } // 額外的可變屬性
}

記錄結構 vs 記錄類 vs 普通結構體

特性 記錄結構 (record struct) 記錄類 (record class) 普通結構體 (struct)
類型 值類型 引用類型 值類型
分配位置 棧(通常) 棧(通常)
默認不可變 是(屬性為 init 是(屬性為 init 否(可變)
值相等性 自動實現 自動實現 需手動實現
with 表達式 支持 支持 不支持
解構 自動支持 自動支持 需手動實現
繼承 不支持 支持 不支持

記錄結構的核心特性

位置記錄結構(Positional Record Structs)

// 位置記錄結構 - 最簡潔的形式
public record struct Person(string FirstName, string LastName, int Age);

// 使用示例
var person = new Person("John", "Doe", 30);
Console.WriteLine(person); // 輸出: Person { FirstName = John, LastName = Doe, Age = 30 }

// 解構
var (firstName, lastName, age) = person;
Console.WriteLine($"{firstName} {lastName}, {age}歲");

值相等性(Value Equality)

public record struct Point(int X, int Y);

// 值相等性比較
var point1 = new Point(1, 2);
var point2 = new Point(1, 2);
var point3 = new Point(3, 4);

Console.WriteLine(point1 == point2); // True - 基於值比較
Console.WriteLine(point1 == point3); // False
Console.WriteLine(point1.Equals(point2)); // True

with 表達式(非破壞性變更)

public record struct Person(string FirstName, string LastName, int Age);

var original = new Person("John", "Doe", 30);

// 創建修改後的副本(非破壞性變更)
var updated = original with { Age = 31 };
var renamed = original with { FirstName = "Jane" };

Console.WriteLine(original); // Person { FirstName = John, LastName = Doe, Age = 30 }
Console.WriteLine(updated);  // Person { FirstName = John, LastName = Doe, Age = 31 }
Console.WriteLine(renamed);  // Person { FirstName = Jane, LastName = Doe, Age = 30 }

自定義行為

// 自定義記錄結構
public record struct Person(string FirstName, string LastName)
{
    // 添加計算屬性
    public string FullName => $"{FirstName} {LastName}";
    
    // 添加方法
    public string GetFormattedName() => $"{LastName}, {FirstName}";
    
    // 重寫ToString
    public override string ToString() => FullName;
    
    // 自定義相等性邏輯(可選)
    public bool Equals(Person other) => 
        FirstName == other.FirstName && LastName == other.LastName;
    
    // 自定義GetHashCode(可選)
    public override int GetHashCode() => 
        HashCode.Combine(FirstName, LastName);
}

// 使用自定義記錄結構
var person = new Person("John", "Doe");
Console.WriteLine(person.FullName); // John Doe
Console.WriteLine(person.GetFormattedName()); // Doe, John
Console.WriteLine(person); // John Doe

高級用法和模式

與模式匹配結合

public record struct Point(int X, int Y);

// 在模式匹配中使用記錄結構
string ClassifyPoint(Point point) => point switch
{
    (0, 0) => "原點",
    (var x, var y) when x == y => "在y=x線上",
    (var x, var y) when x > 0 && y > 0 => "第一象限",
    (var x, var y) when x < 0 && y > 0 => "第二象限",
    (var x, var y) when x < 0 && y < 0 => "第三象限",
    (var x, var y) when x > 0 && y < 0 => "第四象限",
    _ => "在座標軸上"
};

// 使用示例
Console.WriteLine(ClassifyPoint(new Point(0, 0))); // 原點
Console.WriteLine(ClassifyPoint(new Point(3, 3))); // 在y=x線上
Console.WriteLine(ClassifyPoint(new Point(2, 4))); // 第一象限

實現接口

public record struct Vector2D(double X, double Y) : IFormattable
{
    public double Magnitude => Math.Sqrt(X * X + Y * Y);
    
    public string ToString(string format, IFormatProvider formatProvider)
    {
        return format?.ToUpper() switch
        {
            "M" => $"({X}, {Y}) with magnitude {Magnitude:F2}",
            _ => $"({X}, {Y})"
        };
    }
}

// 使用接口實現
var vector = new Vector2D(3, 4);
Console.WriteLine(vector.ToString("M", CultureInfo.InvariantCulture));
// 輸出: (3, 4) with magnitude 5.00

集合中使用 Record Structs

// 高性能點集處理
var points = new Point[1000];
var sum = new Point(0, 0);

for (int i = 0; i < points.Length; i++)
{
    points[i] = new Point(i, i * 2);
    sum = sum with 
    { 
        X = sum.X + points[i].X, 
        Y = sum.Y + points[i].Y 
    };
}

與 Span 和 Memory 結合

Span<Point> points = stackalloc Point[4];
points[0] = new(0, 0);
points[1] = new(0, 1);
points[2] = new(1, 1);
points[3] = new(1, 0);

// 高性能幾何計算
double area = CalculatePolygonArea(points);

實際應用場景

數學和幾何計算

public readonly record struct Rectangle(Point TopLeft, Point BottomRight)
{
    public int Width => BottomRight.X - TopLeft.X;
    public int Height => BottomRight.Y - TopLeft.Y;
    public int Area => Width * Height;
    
    public bool Contains(Point point) =>
        point.X >= TopLeft.X && point.X <= BottomRight.X &&
        point.Y >= TopLeft.Y && point.Y <= BottomRight.Y;
    
    public Rectangle Inflate(int delta) =>
        this with 
        { 
            TopLeft = new Point(TopLeft.X - delta, TopLeft.Y - delta),
            BottomRight = new Point(BottomRight.X + delta, BottomRight.Y + delta)
        };
}

// 使用幾何記錄結構
var rect = new Rectangle(new Point(0, 0), new Point(10, 10));
Console.WriteLine($"面積: {rect.Area}"); // 面積: 100
Console.WriteLine($"包含點 (5,5): {rect.Contains(new Point(5, 5))}"); // True

var largerRect = rect.Inflate(2);
Console.WriteLine($"新面積: {largerRect.Area}"); // 新面積: 196

數據傳輸對象(DTO)

// API 響應DTO
public readonly record struct ApiResponse<T>(T Data, string Error, DateTime Timestamp)
{
    public bool IsSuccess => string.IsNullOrEmpty(Error);
    
    public static ApiResponse<T> Success(T data) => 
        new ApiResponse<T>(data, null, DateTime.UtcNow);
    
    public static ApiResponse<T> Failure(string error) => 
        new ApiResponse<T>(default, error, DateTime.UtcNow);
}

// 使用DTO記錄結構
var successResponse = ApiResponse<string>.Success("操作成功");
var errorResponse = ApiResponse<string>.Failure("發生錯誤");

Console.WriteLine(successResponse.IsSuccess); // True
Console.WriteLine(errorResponse.IsSuccess);   // False

領域模型中的值對象

// 貨幣值對象
public readonly record struct Money(decimal Amount, string Currency)
{
    public static Money operator +(Money left, Money right)
    {
        if (left.Currency != right.Currency)
            throw new InvalidOperationException("貨幣類型不匹配");
        
        return new Money(left.Amount + right.Amount, left.Currency);
    }
    
    public static Money operator *(Money money, decimal factor) =>
        new Money(money.Amount * factor, money.Currency);
    
    public override string ToString() => $"{Amount:F2} {Currency}";
}

// 使用值對象
var price1 = new Money(100.50m, "USD");
var price2 = new Money(50.25m, "USD");
var total = price1 + price2;
var discounted = total * 0.9m;

Console.WriteLine($"總價: {total}");       // 總價: 150.75 USD
Console.WriteLine($"折扣價: {discounted}"); // 折扣價: 135.68 USD

最佳實踐和注意事項

何時使用記錄結構

// ✅ 適合使用記錄結構的場景:
// 1. 小型、簡單的數據結構
public record struct Point(int X, int Y);

// 2. 值語義重要的場景
public record struct Money(decimal Amount, string Currency);

// 3. 性能敏感的場景(避免堆分配)
public record struct Measurement(double Value, string Unit);

// 4. 需要值相等性的場景
public record struct KeyValuePair<TKey, TValue>(TKey Key, TValue Value);

// ❌ 不適合使用記錄結構的場景:
// 1. 大型數據結構(>16字節)
// 2. 需要繼承的場景
// 3. 需要身份標識的場景

適用場景

  • 高性能遊戲開發:3D 座標、向量、顏色
  • 科學計算:矩陣、複數、測量單位
  • 金融系統:貨幣金額、匯率
  • 數據處理管道:中間數據結構
  • 設備通信:協議數據包結構
  • 地理空間計算:座標點、邊界框

總結

C# 10 的記錄結構是一個強大的特性,它結合了結構體的性能優勢和記錄的簡潔性。關鍵要點:

  • 值類型語義:記錄結構是值類型,分配在棧上,性能更好
  • 不可變性:默認提供不可變屬性(使用 init 訪問器)
  • 值相等性:自動實現基於值的相等性比較
  • 簡潔語法:提供位置語法、with 表達式和解構功能
  • 適用場景:小型數據結構、值對象、性能敏感場景
user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.