C#的record类型和class类型有何不同?

星降
发布: 2025-08-02 10:32:02
原创
214人浏览过

record和class都是引用类型,但record默认提供值相等性、不可变性支持、自动重写tostring/gethashcode/equals及with表达式,适合表示数据;class默认基于引用相等、可变,适合表示具有行为和唯一标识的实体。2. 选择record当类型身份由其数据决定(如dto、值对象),选择class当类型强调行为或拥有独立生命周期(如领域实体、服务)。3. 使用record需注意:不可变性是浅层的,引用类型的属性内部仍可变;继承时相等性比较包含所有成员;存在轻微性能开销;不应滥用在需要可变状态或复杂行为的场景。record通过减少样板代码和推广不可变性,解决了数据类定义中的冗余与并发安全问题,是c#对现代编程范式的回应。

C#的record类型和class类型有何不同?

C#中的

record
登录后复制
类型和
class
登录后复制
类型,从根本上讲,它们都是引用类型,但
record
登录后复制
在设计哲学和默认行为上与
class
登录后复制
有着显著的区别,尤其是在处理数据和值语义方面。你可以把
record
登录后复制
看作是C#为了更好地支持不可变数据和值对象而量身定制的一种引用类型。

解决方案

当我们谈论

record
登录后复制
class
登录后复制
的不同,最核心的几点差异在于它们对“相等性”的定义、默认的“可变性”以及一些语法上的便利。

首先,关于相等性。对于

class
登录后复制
,默认情况下,它的相等性是基于引用相等的。这意味着即使两个
class
登录后复制
实例的属性值完全相同,如果它们指向内存中的不同位置,
==
登录后复制
运算符和
Equals()
登录后复制
方法会认为它们是不相等的。这很符合我们对“对象”的直观理解:每个对象都是独一无二的个体。然而,
record
登录后复制
则不同,它默认实现的是值相等。当你比较两个
record
登录后复制
实例时,只要它们所有公共属性(包括字段,如果它们是公共的)的值都相等,那么这两个
record
登录后复制
就被认为是相等的。这在处理数据容器时异常方便,比如你有一个表示坐标的
Point
登录后复制
new Point(1, 2)
登录后复制
和另一个
new Point(1, 2)
登录后复制
,你肯定希望它们是相等的。
record
登录后复制
就是为了这种场景而生,它自动重写了
Equals()
登录后复制
GetHashCode()
登录后复制
以及
==
登录后复制
!=
登录后复制
运算符,省去了我们手动编写这些样板代码的麻烦。

其次,可变性

class
登录后复制
类型默认是可变的,你可以随意修改其公共属性的值。这在很多情况下是必需的,比如一个表示用户会话的
Session
登录后复制
对象,其状态会随着用户的操作而改变。但
record
登录后复制
则倾向于不可变性。虽然你仍然可以在
record
登录后复制
中定义可变的属性(使用
set
登录后复制
),但它强烈推荐使用
init
登录后复制
访问器来创建只读属性,这强制你在对象创建时就设定好所有属性的值,之后就不能再修改了。这种设计哲学在并发编程和函数式编程中尤为重要,因为不可变对象可以安全地在多个线程间共享,无需担心数据竞争问题。而且,
record
登录后复制
还引入了
with
登录后复制
表达式,这是一个非常优雅的特性。当你需要基于现有
record
登录后复制
实例创建新实例,但只修改其中少数几个属性时,
with
登录后复制
表达式能让你以一种非破坏性的方式实现这一点,而不是手动复制所有属性再修改。

再者,是语法糖

record
登录后复制
提供了一种非常简洁的位置记录(positional record)语法。你可以直接在类型声明中定义属性,编译器会自动为你生成一个主构造函数,将这些属性作为参数,并为它们创建
init
登录后复制
访问器。它还会自动生成一个
Deconstruct
登录后复制
方法,方便你将
record
登录后复制
实例的属性解构到单独的变量中。这些都是
class
登录后复制
不具备的,或者说,
class
登录后复制
需要你手动去实现这些功能。

最后,

record
登录后复制
还自动重写了
ToString()
登录后复制
方法,使其默认输出所有公共属性的名称和值,这对于调试和日志记录来说,简直是福音,省去了我们一遍又一遍地编写
ToString()
登录后复制
的麻烦。

为什么C#要引入record类型?它解决了哪些痛点?

说实话,C#引入

record
登录后复制
类型,我觉得是语言发展到一定阶段,对开发者实际痛点的一种回应,也是对现代编程范式的一种拥抱。我们过去写C#代码,尤其是涉及到数据传输对象(DTOs)、值对象或者任何需要表示“纯数据”的结构时,总会遇到一些重复性的工作。

最典型的就是样板代码的冗余。想想看,一个简单的

Person
登录后复制
类,如果你想让
new Person("Alice", 30)
登录后复制
和另一个
new Person("Alice", 30)
登录后复制
被认为是相等的(基于值),你就得手动去重写
Equals()
登录后复制
GetHashCode()
登录后复制
。这不仅繁琐,还容易出错。再比如,为了方便调试,你可能还要重写
ToString()
登录后复制
,让它打印出所有的属性值。这些都是纯粹的“体力活”,而且是每次定义一个数据类都要重复的。
record
登录后复制
的出现,直接把这些“值语义”的样板代码自动化了,极大地提升了开发效率和代码的整洁度。它让我们可以更专注于业务逻辑,而不是那些为了让类型“表现得像个值”而不得不写的额外代码。

另一个痛点是不可变性的推广。在多线程、并发编程日益普遍的今天,可变状态是很多bug的根源。不可变对象天然线程安全,易于推理和测试。虽然

class
登录后复制
也能实现不可变性(通过只读字段或
init
登录后复制
属性),但
record
登录后复制
把它变成了默认和推荐的行为,并且通过
with
登录后复制
表达式提供了一种非常优雅的“非破坏性修改”方式。这让构建基于不可变数据流的系统变得更加自然和简单,减少了副作用,提升了代码的健壮性。对我个人而言,这种默认的不可变性倾向,让我写代码时能更放心地传递数据,不用担心它们在不经意间被修改。

所以,

record
登录后复制
其实是解决了“如何更优雅、更高效地定义和使用值类型或不可变数据类型”这个核心问题。它让C#在处理数据密集型场景时,有了更趁手的工具

在实际开发中,何时选择record,何时选择class?

这是一个非常实用的问题,也是我自己在写代码时会反复思考的。选择

record
登录后复制
还是
class
登录后复制
,并非绝对,更多的是基于你所定义的类型在系统中的“角色”和“行为”。

讯飞星火认知大模型
讯飞星火认知大模型

科大讯飞推出的类ChatGPT AI对话产品

讯飞星火认知大模型28
查看详情 讯飞星火认知大模型

一般来说,当你的类型主要用于表示“数据”或“值”时,

record
登录后复制
是更优的选择。想想那些不需要独立标识符,其身份完全由其包含的数据决定的对象。例如:

  • 数据传输对象(DTOs):从API接收或发送的数据结构,它们通常只是数据的载体,其相等性应该基于内容。
  • 值对象(Value Objects):在领域驱动设计(DDD)中,像
    Money
    登录后复制
    Address
    登录后复制
    Coordinates
    登录后复制
    这类类型,它们没有自己的生命周期或唯一标识,两个
    Money
    登录后复制
    对象如果金额和货币类型都相同,那就是同一个“值”。
  • 不可变配置:应用的配置信息,一旦加载就不应被修改。
  • 临时数据结构:在方法内部或服务之间传递的临时数据。

使用

record
登录后复制
的好处是,你获得了自动的值相等性、漂亮的
ToString()
登录后复制
输出以及
with
登录后复制
表达式带来的便利,这些都能让你的代码更简洁、更安全。

而当你的类型需要表示“实体”或“行为”时,

class
登录后复制
依然是首选
class
登录后复制
更适合那些具有独立生命周期、唯一标识,并且可能包含复杂行为的对象。例如:

  • 领域实体(Domain Entities):在DDD中,像
    User
    登录后复制
    Order
    登录后复制
    Product
    登录后复制
    等,它们有唯一的ID,即使属性值变了,它们仍然是同一个实体。它们的相等性通常是基于ID而不是所有属性。
  • 服务(Services):负责执行业务逻辑的对象,它们通常是单例或有状态的,不适合用
    record
    登录后复制
  • 依赖注入的组件:比如一个数据库上下文、一个日志器,它们通常是单例或作用域生命周期,并且需要管理内部状态。
  • 大型或复杂的对象:如果一个对象内部包含大量可变状态,或者其行为比数据更重要,那么
    class
    登录后复制
    可能更合适。虽然
    record
    登录后复制
    也可以包含可变状态,但它的设计哲学是倾向于不可变性,强行在
    record
    登录后复制
    里塞满可变状态可能会有点“别扭”。

总结一下,如果一个对象是“它是什么”比“它能做什么”更重要,并且它的身份由其值决定,那么

record
登录后复制
是你的朋友。如果一个对象是“它能做什么”更重要,或者它需要一个独立于其值的身份,那么
class
登录后复制
依然是不可替代的基石。

record类型有哪些潜在的陷阱或需要注意的地方?

尽管

record
登录后复制
带来了很多便利,但它并非银弹,使用时还是有些地方需要留意,否则可能会踩到一些小坑。

首先,也是最常见的一个误解:

record
登录后复制
的不可变性是“浅层”的。当你定义一个
record
登录后复制
,并使用
init
登录后复制
属性来确保其不可变时,这只保证了
record
登录后复制
本身的属性引用不能被修改。如果这个
record
登录后复制
的属性是一个引用类型(比如一个
List<string>
登录后复制
或另一个
class
登录后复制
实例),那么这个引用本身是不可变的,但它所指向的对象内部的状态仍然是可变的。

举个例子:

public record UserProfile(string Name, List<string> Permissions);

var profile1 = new UserProfile("Alice", new List<string> { "Read", "Write" });
// 使用 with 表达式创建新的 record 实例,Permissions 列表的引用被复制
var profile2 = profile1 with { Name = "Bob" };

// 但如果你直接修改了 profile1 内部的 Permissions 列表
profile1.Permissions.Add("Delete"); // 这行代码是合法的!

// 此时,profile1 和 profile2 的 Permissions 列表都受到了影响,因为它们引用的是同一个 List<string> 实例
Console.WriteLine(string.Join(", ", profile1.Permissions)); // 输出: Read, Write, Delete
Console.WriteLine(string.Join(", ", profile2.Permissions)); // 输出: Read, Write, Delete
登录后复制

看到没?

profile1.Permissions
登录后复制
这个属性的引用本身不能被重新赋值,但它指向的
List<string>
登录后复制
对象内部是可以被修改的。这会导致一些意想不到的副作用。要实现真正的深度不可变,你需要确保
record
登录后复制
的所有属性都是不可变的类型,或者在复制时进行深度克隆。这通常需要一些手动的工作,或者使用像
ImmutableList<T>
登录后复制
这样的不可变集合。

其次,继承与相等性

record
登录后复制
支持继承,但在继承链中,
Equals()
登录后复制
GetHashCode()
登录后复制
的实现会变得稍微复杂。基
record
登录后复制
Equals
登录后复制
方法会包含一个
PrintMembers
登录后复制
方法,它会检查所有派生
record
登录后复制
的成员是否也相等。这意味着,如果你有一个基
record
登录后复制
和一个派生
record
登录后复制
,即使派生
record
登录后复制
的额外属性都相同,如果基
record
登录后复制
的属性不同,它们依然被认为是不同的。反之亦然。这通常符合预期,但如果你的继承结构复杂,或者你希望在某些情况下忽略派生类的额外属性进行相等性比较,就需要特别注意。

再来,性能考量。虽然对于大多数应用来说,

record
登录后复制
的性能开销通常可以忽略不计,但它毕竟会生成额外的代码(如
Equals
登录后复制
GetHashCode
登录后复制
ToString
登录后复制
Deconstruct
登录后复制
以及
with
登录后复制
表达式的实现),这会略微增加编译后的IL代码量。在极度性能敏感的场景,比如需要创建数百万个微小对象的循环中,这些额外的开销可能会累积。不过,这通常是过度优化了,只有在profiling显示这里是瓶颈时才需要考虑。

最后,就是滥用问题。就像任何新特性一样,

record
登录后复制
也可能被滥用。如果一个对象的主要目的是封装行为,并且其身份由引用而不是值决定,那么坚持使用
class
登录后复制
会更清晰。强行将一个复杂的、有状态的实体定义为
record
登录后复制
,可能会导致代码的可读性和维护性下降,因为它的行为与
record
登录后复制
的设计初衷不符。所以,理解
record
登录后复制
的设计哲学,并在合适的场景使用它,远比盲目跟风重要。

以上就是C#的record类型和class类型有何不同?的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习
PHP中文网抖音号
发现有趣的

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号