导读:本期聚焦于宋琮安创作的《c# 记录 record 类型有什么好处?一文吃透 record 的核心优势》,敬请观看详情。C# 9 引入的 record 类型到底是用来解决什么问题的?简单来说,它让开发者只用一行代码就能声明一个具备值语义的不可变数据类型,编译器自动生成构造函数、属性、Equals、GetHashCode、ToString 以及 DeepCopy 等成员。本文围绕 record 的核心优势展开:先剖析传统 class 在相等性比较上的引用语义陷阱,再通过 with 表达式演示非破坏性修改数据的优雅写法,接着对比 record class 与 record struct 的差异,最后结合 DTO、领域建模等实际场景说明何时该选 record、何时仍该用普通 class,帮助你写出更简洁、更不易出错的 C# 代码。

C# 9 引入的 record(记录)类型,可以说是近几个版本里对日常开发影响最大的语言特性之一。它并不是一个全新的类型体系,而是编译器帮你自动生成大量样板代码的语法糖。如果你写过实体类、DTO、配置对象,一定会对满屏的构造函数、属性初始化、Equals、GetHashCode、ToString 透着一股疲惫感,record 就是为了把这些工作全部交给编译器,让你只关心数据本身的结构。这篇文章就来详细聊聊 record 到底能带来哪些好处,以及它在实际项目中该如何使用。

c# 记录 record 类型有什么好处?一文吃透 record 的核心优势

一、值语义的相等性比较:record 最核心的优势

要理解 record 的价值,得先从传统 class 的一个常见坑说起。在 C# 中,class 是引用类型,两个对象即層使所有属性完全相同,使用 Equals 比较的结果也是 false,因为默认比较的是引用地址,也就是它们是不是同一个对象。这在处理数据传递、单元测试断言、集合去重等场景时,经常让人踩坑。

record 类型天生就是值语义:编译器会自动重写 Equals== 运算符和 GetHashCode,按照所有公开属性的值逐一比较。看一个对比示例就非常直观。

// 传统 class:比较引用,结果为 False
public class PersonClass
{
    public string Name { get; init; }
    public int Age { get; init; }
}

// record:比较值,结果为 True
public record PersonRecord(string Name, int Age);

var c1 = new PersonClass { Name = "张三", Age = 20 };
var c2 = new PersonClass { Name = "张三", Age = 20 };
Console.WriteLine(c1.Equals(c2)); // False

var r1 = new PersonRecord("张三", 20);
var r2 = new PersonRecord("张三", 20);
Console.WriteLine(r1.Equals(r2)); // True

注意上面 record 的声明只有一行。这叫位置参数(positional parameter)写法,编译器会自动生成主构造函数、名为 NameAge 的只读属性,以及 Deconstruct 解构方法,可以直接写成 var (name, age) = r1;。如果换成手写 class,同样的功能至少需要二三十行代码。

值语义带来的实际收益不只是少写代码。在集合操作中,把 record 放进 HashSet<T>、用 Distinct() 去重、在测试中 Assert.Equal(expected, actual),行为都符合直觉。而且 record 生成的 ToString 会打印出所有属性名和值,调试时一眼就能看清对象内容,比 class 默认输出的类型名实用得多。

二、不可变性与 with 表达式:非破坏性修改数据

record 的另一个重要好处是天生鼓励不可变设计。位置参数生成的属性是 init 只读属性,对象一旦创建就不能修改。不可变对象有很多工程上的好处:天然线程安全、可以被安全地缓存和共享、不会出现一个地方改了对象导致另一个地方逻辑被污染的问题。

但不可变带来的问题是:想改一个字段怎么办?总不能手动 new 一个新对象把所有属性抄一遍吧。C# 专门为 record 设计了 with 表达式来解决这个问题,它基于对象当前值创建一个副本,只修改指定的属性。

public record Order(string OrderId, decimal Amount, string Status, DateTime CreatedAt);

var order = new Order("A001", 99.9m, "Created", DateTime.Now);

// 只修改状态,其他属性原样复制
var paidOrder = order with { Status = "Paid" };

Console.WriteLine(order.Status);     // Created
Console.WriteLine(paidOrder.Status); // Paid

with 表达式的实现原理是编译器生成了一个受保护的 Clone 方法,复制时浅拷贝所有字段,然后应用你指定的修改。原对象完全不受影响,这种非破坏性更新的风格在函数式编程里非常常见,配合 LINQ 使用特别自然。

值得一提的是,record 的不可变并不彻底到不能包含可变成员,你仍然可以手动添加可变属性或者用可变集合。但从设计意图上讲,record 适合承载不变化的快照型数据,例如从数据库读出来的查询结果、反序列化得到的 JSON 数据、跨线程传递的消息等。这种明确不可变的约定,能让代码阅读者对对象的生命周期有清晰预期。

三、继承与结构化比较:record 比匿名类型和 struct 更灵活

record 是可以继承的,这一点和匿名对象不同,也和 struct 不同。子类 record 会正确处理相等性比较中的类型问题,两个不同类型的 record 即使属性值相同也不会判定为相等,避免了继承体系下比较逻辑混乱的隐患。

public abstract record Shape
{
    public abstract double Area();
}

public record Circle(double Radius) : Shape
{
    public override double Area() => Math.PI * Radius * Radius;
}

public record Rectangle(double Width, double Height) : Shape
{
    public override double Area() => Width * Height;
}

// 结合模式匹配,代码非常简洁
public static string Describe(Shape shape) => shape switch
{
    Circle { Radius: > 10 } c => $"大圆,面积 {c.Area():F1}",
    Circle c => $"小圆,面积 {c.Area():F1}",
    Rectangle r => $"矩形,面积 {r.Area():F1}",
    _ => "未知图形"
};

record 和模式匹配是绝配。因为 record 会生成 Deconstruct 方法,可以直接使用位置模式,比如 Circle c when c.Radius > 10 或者直接 Circle(15) 这种写法匹配属性值。在处理复杂业务分支时,这比一堆 if-else 判断属性值优雅太多。

另外需要区分几个容易混淆的概念。C# 10 之后还支持 record struct,它是值类型的记录,分配在栈上(或内联在包含对象中),适合小而高频创建的数据结构;而默认的 record class 是引用类型,复制的是引用,比较的是值。还有一点,record 默认执行的是浅拷贝,如果包含引用类型属性(比如 List),克隆副本和原对象会共享同一个集合,这一点在可变嵌套结构中要格外小心。

四、什么场景该用 record,什么场景该用 class

说了这么多好处,并不是说 record 要全面取代 class。两者的定位不同,选择时可以从数据的行为特征来判断。

适合 record 的场景:

  • DTO 和 API 请求响应模型:数据只负责承载和传输,不需要复杂行为,record 一行声明搞定。
  • 领域建模中的值对象:比如金额、坐标、时间区间,这类对象天然是值语义,record 的相等性行为完全匹配。
  • 配置项和不可变消息:跨线程传递的命令、事件、快照数据。
  • 单元测试中的期望值构造:值语义让断言直接比较内容。

仍然适合普通 class 的场景:实体有唯一标识且生命周期内状态会变化(典型的 EF Core 实体聚合根)、对象内部需要封装大量行为逻辑而非数据、需要引用相等语义(比如某些缓存键设计)。EF Core 从 6.0 开始也支持用 record 做实体类型,但对于会被跟踪修改的实体,普通 class 更省心。

最后给一个简单的判断口诀:如果两个对象属性全一样就应该算相等,那用 record;如果对象身份本身比内容更重要,那用 class。这条原则能覆盖绝大多数选择困难的情况。善用 record,可以显著减少样板代码、降低可变状态带来的隐患,让你的 C# 代码更简洁、更可靠。

C# record记录类型值语义修改时间:2026-09-14 09:20:50

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/20260914/56597.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。