首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >为什么存在Records?|Records vs class |完整的开发人员决策指南

为什么存在Records?|Records vs class |完整的开发人员决策指南

作者头像
郑子铭
发布2025-01-11 19:31:05
发布2025-01-11 19:31:05
9600
代码可运行
举报
运行总次数:0
代码可运行

Record 到底是什么?让我们消除困惑

把 record 想象成一个写有特定鸡尾酒及其配料的饮品菜单,而 class 则像是一所教你创造无限饮品变化的调酒学校。在深入技术细节之前,让我们先理解 record 要解决的问题:

使用传统的类方式 — 仅仅是为了保存一些数据就要写这么多代码!

代码语言:javascript
代码运行次数:0
运行
复制
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 方式 — 实现完全相同的功能!

代码语言:javascript
代码运行次数:0
运行
复制
public record PersonRecord(string FirstName, string LastName);

📝 我们将继续使用这个相同的类和记录示例!

为什么会有 Records?

record 的引入是因为开发者花费太多时间编写重复的代码来处理数据!开玩笑的.. 以下是使用 record 自动获得的功能:

不可变性

代码语言:javascript
代码运行次数:0
运行
复制
var person = new PersonRecord("John", "Doe");

person.FirstName = "Jane";  // 这行代码无法编译

// 相反,你需要创建一个带有更改的新记录:
var updatedPerson = person with { FirstName = "Jane" };

基于值的相等性比较(这很重要!)

使用类:

代码语言:javascript
代码运行次数:0
运行
复制
var person1 = new PersonClass("John", "Doe");
var person2 = new PersonClass("John", "Doe");
Console.WriteLine(person1 == person2); // False!不同的引用

使用 Records:

代码语言:javascript
代码运行次数:0
运行
复制
var record1 = new PersonRecord("John", "Doe");
var record2 = new PersonRecord("John", "Doe");
Console.WriteLine(record1 == record2); // True!相同的数据 = 相等

轻松复制并修改

代码语言:javascript
代码运行次数:0
运行
复制
var original = new PersonRecord("John", "Doe");

// 创建一个只改变 FirstName 的新记录:
var updated = original with { FirstName = "Jane" };

但是你知道吗!Records 稍微有点慢..让我们来看看

为什么 Records 会(稍微)慢一些?

与类相比,Records 有一点性能开销。但为什么会这样,以及为什么这通常并不重要:

代码语言:javascript
代码运行次数:0
运行
复制
// 基准测试:创建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

开销来自于:

  1. 生成的相等性方法
  2. 基于值的比较代码
  3. 额外的安全检查

现在,你一定在想为什么要不顾这些开销也要使用 Records?

为什么要不顾开销也要使用 Records...

开发者生产力

对于 API 响应,如果我们使用类,则需要大量代码:

代码语言:javascript
代码运行次数:0
运行
复制
public classApiResponseClass<T>
{
    publicT Data {get;init;}
    publicbool Success {get;init;}
    publicstring? Message {get;init;}
    publicDateTime Timestamp {get;init;}

    // 需要构造函数
    // 需要相等性比较
    // 需要 ToString
    // 需要哈希码
    // 太多样板代码!
}

使用 record — 一行搞定!

代码语言:javascript
代码运行次数:0
运行
复制
public record ApiResponseRecord<T>(T Data, bool Success, string? Message, DateTime Timestamp);

不可变性 = 线程安全

因为 records 是不可变的,所以这是线程安全的:

代码语言:javascript
代码运行次数:0
运行
复制
public recordConfiguration(
    string ApiKey,
    string BaseUrl,
    int Timeout
);

// 可以安全地在线程间共享
publicclassService
{
    privatereadonlyConfiguration _config;
    
    publicService(Configuration config)
    {
        _config = config;
    }
    
    // 不需要锁 - 配置无法更改!
}

非常适合领域事件

Records 非常适合事件 — 它们是已发生的事实

代码语言:javascript
代码运行次数:0
运行
复制
public recordOrderPlaced(
    Guid OrderId,
    string CustomerEmail,
    decimal Amount,
    DateTime PlacedAt
);

publicrecordPaymentReceived(
    Guid OrderId,
    string TransactionId,
    decimal Amount,
    DateTime PaidAt
);

🚩 这些是不可变的事实 — 它们永远不应该改变!

该做与不该做

1. 深层 Record 层次结构可能会很慢

❌ 不要这样做:

代码语言:javascript
代码运行次数:0
运行
复制
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);

为什么?每次相等性检查都必须遍历整个层次结构!

✔️ 使用组合:

代码语言:javascript
代码运行次数:0
运行
复制
public record Manager(
    Guid Id,
    PersonInfo Person,
    EmployeeInfo Employment,
    string Department
);

2. 使用集合时要小心

❌ 问题代码:

代码语言:javascript
代码运行次数:0
运行
复制
public recordUserList(List<User> Users)
{
    publicUserListAddUser(User user)=>
        thiswith{ Users =newList<User>(Users){ user }};
}

这每次都会创建一个新列表!

✔️ 更好的方式:

代码语言:javascript
代码运行次数:0
运行
复制
public classUserCollection
{
    privatereadonlyList<User> _users =new();
    publicIReadOnlyList<User> Users => _users.AsReadOnly();
    
    publicvoidAddUser(User user)=> _users.Add(user);
}

让我们看看实际示例

1. API 契约

代码语言:javascript
代码运行次数:0
运行
复制
public recordCreateUserRequest(
    string Email,
    string Password,
    string FirstName,
    string LastName
);

publicrecordCreateUserResponse(
    Guid UserId,
    string Email,
    DateTime CreatedAt
);

2. 领域事件

代码语言:javascript
代码运行次数:0
运行
复制
public record OrderShipped(
    Guid OrderId,
    string TrackingNumber,
    DateTime ShippedAt,
    Address ShippingAddress
);

3. 配置

代码语言:javascript
代码运行次数:0
运行
复制
public record DatabaseConfig(
    string ConnectionString,
    int MaxConnections,
    TimeSpan Timeout,
    bool EnableRetry
);

4. DDD中的值对象

代码语言:javascript
代码运行次数:0
运行
复制
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 };
    }
}

在我们结束本章之前,记住这个表格:

代码语言:javascript
代码运行次数:0
运行
复制
| **适合使用 Records 的场景**  | **避免使用 Records 的场景** |
|--------------------------|------------------------|
| DTOs 和 API 契约          | 需要频繁更新的对象           |
| 配置对象                  | 深层继承层次结构             |
| 领域事件                  | 大型可变集合               |
| 值对象                   | 复杂业务逻辑               |
| 任何不可变数据结构           |                        |

C# 中的 Records 不仅仅是语法糖 — 它们是以安全、不可变方式处理数据的强大工具。虽然它们带来了一些小的性能开销,但减少代码量、自动相等性比较和不可变性带来的好处通常远远超过了这些成本!

  • Records = 不可变数据容器
  • Classes = 带有行为的可变对象
  • 根据需求选择,而不是根据性能
  • 注意层次结构和集合的使用

现在你完全理解了何时以及为什么在 C# 应用程序中使用 records!

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-01-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 DotNet NB 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Record 到底是什么?让我们消除困惑
    • 为什么会有 Records?
      • 不可变性
      • 基于值的相等性比较(这很重要!)
      • 轻松复制并修改
    • 为什么 Records 会(稍微)慢一些?
    • 为什么要不顾开销也要使用 Records...
      • 开发者生产力
      • 不可变性 = 线程安全
      • 非常适合领域事件
    • 该做与不该做
      • 1. 深层 Record 层次结构可能会很慢
      • 2. 使用集合时要小心
    • 让我们看看实际示例
      • 1. API 契约
      • 2. 领域事件
      • 3. 配置
      • 4. DDD中的值对象
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档