textlize pricing account
When Should You Use record, class, or struct - And Why That Matters?
Cover

00:09:28

C# 类型选择指南:何时使用 record、class 或 struct?

在 C# 开发中,选择合适的类型(record、class 或 struct)对代码的行为、性能和可维护性有深远影响。本文将从语法、行为和性能三个维度,深入分析如何做出明智的选择。

核心概念:值类型与引用类型

C# 继承了 C++ 和 Java 的类型系统精髓,其根本区别在于:class 是引用类型,而 struct 是值类型。这一区别影响了参数传递、内存分配和对象行为的方方面面。

当参数传递给方法时,C# 默认采用按值传递。对于引用类型,复制的是对象的引用;对于值类型,复制的则是整个对象本身。

一、语法与设计哲学

1.1 Struct 的不可变性原则

值类型的复制机制导致了一个重要特性:即使将 struct 的属性声明为可变(mutable),修改操作也极易产生 bug。因为方法内部修改的是副本,而非原始对象。

这决定了值类型的设计应遵循不可变(immutable)原则,并采用写时复制(copy-on-write)技术。任何修改都应返回一个新实例,而非在原有实例上直接变更。

遗憾的是,C# 编译器不会警告未使用返回值的调用,这需要开发者自觉遵守不可变模式。

1.2 Class 的灵活性

引用类型(class)则提供了更多灵活性。你可以选择将其设计为可变或不可变,这完全取决于你的业务场景和设计意图。

二、性能考量

性能是选择类型时的重要考虑因素,但需要避免陷入误区。

2.1 小 Struct 的性能优势

包含少量(通常建议 1-2 个)字段的 struct 由于避免堆(heap)分配,性能表现极其出色。

2.2 大 Struct 的性能陷阱

当 struct 包含的字段增多(例如达到 3 个或更多),频繁复制整个实例的成本会急剧上升,性能可能反而不如引用类型。

最终的性能表现取决于实例被复制的次数和规模,需要根据具体场景进行权衡和测试。

2.3 JIT 编译的底层差异

使用泛型时,.NET 的 JIT 编译器会为不同大小的 struct 生成不同的底层实现代码,而为所有引用类型(引用大小固定)生成同一份实现。这是一个精巧的设计,开发者通常无需关心,但它解释了 .NET 泛型性能优于 Java 泛型的原因之一。

三、Record 类型的革命

Record 类型的引入是为了更好地支持值语义(value semantics),即使其本身是引用类型。

3.1 Record Class 的特性

将 class 声明为 record 后,编译器会自动为其生成:

  • 公共的不可变属性
  • 支持非破坏性变更(即写时复制)的 with 表达式
  • 基于内容(content-based)而非引用(reference-based)的 EqualsGetHashCode 实现和相等运算符

这使得 record 实例虽然存储在堆上,但行为上像一个值。

3.2 应用场景的优势

基于值的语义让 record 在特定场景下表现卓越:

  • 作为 HashSet 的元素或 Dictionary 的键:基于内容的比较和哈希使得查找和去重工作符合直觉。
  • 序列化/反序列化:作为 DTO (Data Transfer Object) 的理想选择。
  • 非破坏性变更with 表达式允许你仅指定需要修改的属性,其余属性自动复制到新实例。后续为 record 添加新属性,所有现有的 with 表达式无需修改仍可正常工作,极大地提升了代码的可维护性。

3.3 Record Struct

C# 后续版本引入了 record struct。其语法与 record class 相似,但本质是值类型。关键区别在于:

  • 编译器同样会为其生成基于内容的相等性成员,性能优于手动实现或反射实现的传统 struct。
  • 默认情况下,record struct可变的。若要强制不可变,需显式声明为 readonly record struct

实践建议总结

类型 核心特征 推荐使用场景
Record Class 引用类型,不可变,值语义 DTO、不可变模型、哈希集合元素、字典键、任何需要值语义且数据量可能较大的场景
Class 引用类型,可变/不可变灵活 EF Core 实体、需要身份标识和可变状态的核心领域模型
Readonly Record Struct 值类型,不可变,值语义 小型、轻量级的值对象(如包含不超过 2 个值类型组件的坐标、键值对等),对性能有极致要求

现代 C# 开发类型选择策略

Record classes 已成为许多 immutable 设计的首选,特别是在序列化和 DTO 领域。

传统的 mutable classes 通常保留给需要身份标识和可变状态的实体(Entity),尤其是在与 Entity Framework Core 配合使用时。

Readonly record structs 则适用于非常小型的、不可变的值对象,它能提供最佳的性能表现。

© 2025 textlize.com. all rights reserved. terms of services privacy policy