把 record 想象成一个写有特定鸡尾酒及其配料的饮品菜单,而 class 则像是一所教你创造无限饮品变化的调酒学校。在深入技术细节之前,让我们先理解 record 要解决的问题:
使用传统的类方式 — 仅仅是为了保存一些数据就要写这么多代码!
public classPersonClass
{
publicstring FirstName {get;init;}
publicstring LastName {get;init;}
publicPersonClass(string firstName,string lastName)
{
FirstName = firstName;
LastName = lastName;
}
// 需要实现相等性比较
publicoverrideboolEquals(object? obj)
{
if(obj isnotPersonClass other)returnfalse;
return FirstName == other.FirstName &&
LastName == other.LastName;
}
// 集合需要这个
publicoverrideintGetHashCode()
{
return HashCode.Combine(FirstName, LastName);
}
// 调试需要这个
publicoverridestringToString()
{
return$"Person {{ FirstName = {FirstName}, LastName = {LastName} }}";
}
}
使用新的 record 方式 — 实现完全相同的功能!
public record PersonRecord(string FirstName, string LastName);
📝 我们将继续使用这个相同的类和记录示例!
record 的引入是因为开发者花费太多时间编写重复的代码来处理数据!开玩笑的.. 以下是使用 record 自动获得的功能:
var person = new PersonRecord("John", "Doe");
person.FirstName = "Jane"; // 这行代码无法编译
// 相反,你需要创建一个带有更改的新记录:
var updatedPerson = person with { FirstName = "Jane" };
使用类:
var person1 = new PersonClass("John", "Doe");
var person2 = new PersonClass("John", "Doe");
Console.WriteLine(person1 == person2); // False!不同的引用
使用 Records:
var record1 = new PersonRecord("John", "Doe");
var record2 = new PersonRecord("John", "Doe");
Console.WriteLine(record1 == record2); // True!相同的数据 = 相等
var original = new PersonRecord("John", "Doe");
// 创建一个只改变 FirstName 的新记录:
var updated = original with { FirstName = "Jane" };
但是你知道吗!Records 稍微有点慢..让我们来看看
与类相比,Records 有一点性能开销。但为什么会这样,以及为什么这通常并不重要:
// 基准测试:创建100万个实例
publicclassPerformanceComparison
{
privateconstint Iterations =1_000_000;
[Benchmark]
publicvoidCreateClasses()
{
for(int i =; i < Iterations; i++)
{
var person =newPersonClass("John","Doe");
}
}
[Benchmark]
publicvoidCreateRecords()
{
for(int i =; i < Iterations; i++)
{
var person =newPersonRecord("John","Doe");
}
}
}
结果(近似值):类:~45ms || Records:~48ms
开销来自于:
现在,你一定在想为什么要不顾这些开销也要使用 Records?
对于 API 响应,如果我们使用类,则需要大量代码:
public classApiResponseClass<T>
{
publicT Data {get;init;}
publicbool Success {get;init;}
publicstring? Message {get;init;}
publicDateTime Timestamp {get;init;}
// 需要构造函数
// 需要相等性比较
// 需要 ToString
// 需要哈希码
// 太多样板代码!
}
使用 record — 一行搞定!
public record ApiResponseRecord<T>(T Data, bool Success, string? Message, DateTime Timestamp);
因为 records 是不可变的,所以这是线程安全的:
public recordConfiguration(
string ApiKey,
string BaseUrl,
int Timeout
);
// 可以安全地在线程间共享
publicclassService
{
privatereadonlyConfiguration _config;
publicService(Configuration config)
{
_config = config;
}
// 不需要锁 - 配置无法更改!
}
Records 非常适合事件 — 它们是已发生的事实
public recordOrderPlaced(
Guid OrderId,
string CustomerEmail,
decimal Amount,
DateTime PlacedAt
);
publicrecordPaymentReceived(
Guid OrderId,
string TransactionId,
decimal Amount,
DateTime PaidAt
);
🚩 这些是不可变的事实 — 它们永远不应该改变!
❌ 不要这样做:
public recordEntity(Guid Id);
publicrecordPerson(Guid Id,string Name):Entity(Id);
publicrecordEmployee(Guid Id,string Name,decimal Salary):Person(Id, Name);
publicrecordManager(Guid Id,string Name,decimal Salary,string Department)
:Employee(Id, Name, Salary);
为什么?每次相等性检查都必须遍历整个层次结构!
✔️ 使用组合:
public record Manager(
Guid Id,
PersonInfo Person,
EmployeeInfo Employment,
string Department
);
❌ 问题代码:
public recordUserList(List<User> Users)
{
publicUserListAddUser(User user)=>
thiswith{ Users =newList<User>(Users){ user }};
}
这每次都会创建一个新列表!
✔️ 更好的方式:
public classUserCollection
{
privatereadonlyList<User> _users =new();
publicIReadOnlyList<User> Users => _users.AsReadOnly();
publicvoidAddUser(User user)=> _users.Add(user);
}
public recordCreateUserRequest(
string Email,
string Password,
string FirstName,
string LastName
);
publicrecordCreateUserResponse(
Guid UserId,
string Email,
DateTime CreatedAt
);
public record OrderShipped(
Guid OrderId,
string TrackingNumber,
DateTime ShippedAt,
Address ShippingAddress
);
public record DatabaseConfig(
string ConnectionString,
int MaxConnections,
TimeSpan Timeout,
bool EnableRetry
);
public recordMoney(decimal Amount,string Currency)
{
publicstaticMoneyZero(string currency)=>new(, currency);
publicMoneyAdd(Money other)
{
if(Currency != other.Currency)
thrownewInvalidOperationException("Currency mismatch");
returnthiswith{ Amount = Amount + other.Amount };
}
}
在我们结束本章之前,记住这个表格:
| **适合使用 Records 的场景** | **避免使用 Records 的场景** |
|--------------------------|------------------------|
| DTOs 和 API 契约 | 需要频繁更新的对象 |
| 配置对象 | 深层继承层次结构 |
| 领域事件 | 大型可变集合 |
| 值对象 | 复杂业务逻辑 |
| 任何不可变数据结构 | |
C# 中的 Records 不仅仅是语法糖 — 它们是以安全、不可变方式处理数据的强大工具。虽然它们带来了一些小的性能开销,但减少代码量、自动相等性比较和不可变性带来的好处通常远远超过了这些成本!
现在你完全理解了何时以及为什么在 C# 应用程序中使用 records!