簡介
record 是 C# 9 引入的新引用類型(Reference Type),專門用於數據導向(Data-Oriented)的不可變對象。特別適合用於表示不可變的數據傳輸對象(DTO)、值對象和領域模型。
⚡ 主要特性:
- 內置值相等性:兩個
record實例如果屬性值相同,則被認為相等(值相等)。 - 簡潔語法:通過“主構造函數”直接定義屬性。
- 不可變設計:推薦使用
init訪問器,實現只讀屬性。 - 模式匹配友好:可以用
with、解構等簡化數據處理。
基本語法
簡單聲明
public record Person(string FirstName, string LastName);
record關鍵字定義一個不可變引用類型。(string FirstName, string LastName)定義主構造函數和自動init屬性。
等價於:
public class Person
{
public string FirstName { get; init; }
public string LastName { get; init; }
public Person(string firstName, string lastName)
=> (FirstName, LastName) = (firstName, lastName);
// 自動生成值相等比較、Deconstruct 方法等
}
使用:
var p1 = new Person("Alice", "Smith");
Console.WriteLine(p1.FirstName); // Alice
不可變性(init-only)
Record 的屬性默認是隻讀的(init-only),這意味着它們只能在初始化時設置
public record Person(string FirstName, string LastName, int Age);
// 創建 record 實例
var person = new Person("John", "Doe", 30);
// 編譯錯誤:不能修改屬性
// person.Age = 31;
值相等(Value Equality)
引用類型 vs 值類型對比
| 類型 | 相等比較(默認) |
|---|---|
class |
引用相等(Reference Equality) |
struct |
值相等(字段逐一比較) |
record |
值相等(自動生成 Equals/==) |
示例:
var p1 = new Person("Alice", "Smith");
var p2 = new Person("Alice", "Smith");
Console.WriteLine(p1 == p2); // True
Console.WriteLine(p1.Equals(p2)); // True
即使 p1 和 p21 是不同實例,只要屬性值相同,就相等。
with 表達式(非破壞性複製)
record 可以使用 with 表達式複製並修改部分屬性:
var p1 = new Person("Alice", "Smith");
var p2 = p1 with { LastName = "Johnson" };
Console.WriteLine(p2.FirstName); // Alice
Console.WriteLine(p2.LastName); // Johnson
p1保持不變,p2是新的實例。- 這是不可變對象的核心優勢。
解構(Deconstruction)
編譯器會自動生成 Deconstruct 方法:
var person = new Person("Alice", "Smith");
var (first, last) = person;
Console.WriteLine($"{first} {last}");
可直接用元組解構。
繼承與多態
record 支持繼承,且保留值相等語義:
public record Person(string Name);
public record Student(string Name, int Grade) : Person(Name);
Person p = new Student("Alice", 5);
Console.WriteLine(p is Student); // True
自動生成的 Equals 會包含類型檢查,子類與父類不同類型即使屬性相同也不相等。
顯式屬性定義
可以不用主構造函數,像 class 一樣定義:
public record Car
{
public string Brand { get; init; }
public string Model { get; init; }
}
與 class 的唯一區別是自動生成了值相等比較。
record struct(值類型記錄,C# 10+)
C# 10 引入 record struct,結合了 record 的值相等 和 struct 的值類型特性。
// Record struct(值類型)
public record struct Point(int X, int Y);
// 使用 record struct
var point1 = new Point(3, 4);
var point2 = new Point(3, 4);
Console.WriteLine(point1 == point2); // True
Console.WriteLine(point1); // Point { X = 3, Y = 4 }
// 修改 record struct(因為是值類型,所以可以修改)
point1.X = 5;
Console.WriteLine(point1); // Point { X = 5, Y = 4 }
// 只讀 record struct
public readonly record struct ImmutablePoint(int X, int Y);
var immutablePoint = new ImmutablePoint(3, 4);
// immutablePoint.X = 5; // 編譯錯誤:不能修改只讀字段
記錄的成員生成
編譯器為 record 自動生成以下內容:
Equals(object?)和GetHashCode():基於屬性值。==和!=運算符。Deconstruct方法。ToString():輸出形如Person { FirstName = Alice, LastName = Smith }。
這使得 record 非常適合作為 DTO、查詢結果、配置數據。
與 class / struct 對比
| 特性 | class |
struct |
record(class) |
record struct |
|---|---|---|---|---|
| 類型 | 引用類型 | 值類型 | 引用類型 | 值類型 |
| 相等性 | 引用相等 | 值相等 | 值相等 | 值相等 |
| 默認不可變 | ❌(可變) | ❌(可變) | ✅(推薦 init) |
✅(推薦 readonly) |
| 內存分配 | 堆 | 棧/堆 | 堆 | 棧/堆 |
| 繼承 | 支持 | 僅接口 | 支持 | 僅接口 |
with 表達式 |
❌ | ❌ | ✅ | ✅ |
高級用法
與模式匹配
if (person is Person { FirstName: "Alice" })
Console.WriteLine("Found Alice!");
public abstract record Shape;
public record Circle(double Radius) : Shape;
public record Rectangle(double Width, double Height) : Shape;
public record Triangle(double Base, double Height) : Shape;
public static double CalculateArea(Shape shape)
{
return shape switch
{
Circle c => Math.PI * c.Radius * c.Radius,
Rectangle r => r.Width * r.Height,
Triangle t => 0.5 * t.Base * t.Height,
_ => throw new ArgumentException("Unknown shape")
};
}
// 使用模式匹配
var circle = new Circle(5);
var rectangle = new Rectangle(4, 6);
Console.WriteLine(CalculateArea(circle)); // 78.53981633974483
Console.WriteLine(CalculateArea(rectangle)); // 24
位置記錄 + 解構
public record Order(int Id, decimal Price);
var order = new Order(1001, 99.99m);
var (id, price) = order; // 自動解構
實際應用場景
DTO(數據傳輸對象)
// API 響應 DTO
public record ApiResponse<T>(bool Success, string Message, T Data);
// API 請求 DTO
public record CreateUserRequest(string Username, string Email, string Password);
// 使用 record DTO
public class UserController : ControllerBase
{
[HttpPost]
public IActionResult CreateUser(CreateUserRequest request)
{
// 處理請求...
var response = new ApiResponse<User>(true, "User created successfully", user);
return Ok(response);
}
}
配置對象
// 應用程序配置
public record AppSettings
{
public string ConnectionString { get; init; }
public int MaxRetryAttempts { get; init; } = 3;
public TimeSpan Timeout { get; init; } = TimeSpan.FromSeconds(30);
public LoggingSettings Logging { get; init; }
}
public record LoggingSettings
{
public string Level { get; init; } = "Information";
public string FilePath { get; init; } = "logs/app.log";
}
// 使用配置
var settings = new AppSettings
{
ConnectionString = "Server=localhost;Database=MyDb",
MaxRetryAttempts = 5,
Logging = new LoggingSettings { Level = "Debug" }
};
適用場景
推薦使用 record 的場景:
- 不可變的數據模型:
DTO、API響應對象 - 配置項、設置類
- 數據庫查詢結果(
EF Core中很常見) - 事件/消息模型(
Event Sourcing、CQRS)
不推薦使用 record 的場景:
- 對象需要頻繁修改。
- 需要嚴格的引用語義(例如緩存中的唯一實例)。
性能與注意事項
record是引用類型(除非是record struct),所以在堆上分配,仍有GC開銷。- 值比較可能略微增加
Equals/GetHashCode的計算成本。 with表達式會創建新對象,不要在高頻場景頻繁使用。
總結
| 特性 | 説明 |
|---|---|
| 核心目標 | 簡化不可變數據類型的聲明和比較 |
| 類型 | 引用類型(默認)/值類型(record struct) |
| 相等性 | 自動實現基於值的相等性(Equals、==) |
| 不可變性 | 推薦使用 init 或 readonly |
| 複製 | with 表達式實現非破壞性複製 |
| 解構 | 自動生成 Deconstruct,支持元組解構 |
| 典型應用 | DTO、API 數據模型、配置對象、事件/消息對象 |