簡介
Record Structs 是一種值類型的記錄(record),結合了 struct 的值語義和 record 的功能(如自動生成相等性比較、不可變性支持)。它們是 C# 9.0 中引入的引用類型 record(默認 class)的擴展,專為性能敏感場景設計,特別是在需要棧分配或避免 GC 壓力的情況下。
核心特性
- 值類型:存儲在棧上(除非裝箱),避免堆分配,適合小數據結構。
- 不可變性:默認鼓勵不可變設計(通過
init-only屬性),但可選擇可變。 - 值相等性:自動實現基於內容的相等性比較(== 和
Equals)。 - 自動
ToString:生成人類可讀的字符串表示。 - 解構支持:自動提供
Deconstruct方法,方便模式匹配和解構。 With表達式:支持非破壞性變異(創建新實例)。- 繼承支持:支持繼承其他
record structs(但不能繼承class或record 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表達式和解構功能 - 適用場景:小型數據結構、值對象、性能敏感場景