在 C# 的世界里, record(记录类型)的出现是为了解决一个老生常谈的问题: 如何用最少的代码,定义一个纯粹用来存放数据的类型。

它本质上是一个特殊的 class(默认情况下),但编译器会自动为你生成大量关于“数据处理”的模板代码。

如果说 class 是为了行为(做事)设计的,那么 record 就是专门为了数据(存值)而生的。它结合了 class 的引用类型特性和元组的简便性,是目前 C# 定义“数据模型”的首选

C# 处理数据的三大法宝:ref/out(手段)、Tuple(轻量包)、Record(正式模型)

1. 核心语法

在没有 record 之前,你要写一个具备“值相等”特性的数据类,需要写几十行。现在只需要一行:

// 这一行叫做“位置参数”语法(Positional Records)
public record User(int Id, string Name, string Email);

// 这行代码自动生成了:属性、构造函数、解构函数、Equals、GetHashCode、ToString...
public record User(int Id, string Name, string Role);

// 使用
var user1 = new User(1, "老王", "Admin");

编译器在幕后偷偷帮你做了:

  • 定义了 IdNameEmail 三个只读属性(init-only)。
  • 生成了一个包含这三个参数的构造函数
  • 重写了 EqualsGetHashCode== 操作符(按值比较)。
  • 生成了 ToString(),输出格式如 User { Id = 1, Name = "..." }
  • 生成了 Deconstruct(解构)方法,支持 var (id, name, email) = user;

2. 深度特性:为什么它比普通 Class 强?

A. 值的相等性(Value Equality)

普通类比较的是“内存地址”,而 record 比较的是“属性值”。

var u1 = new User(1, "张三", "a@b.com");
var u2 = new User(1, "张三", "a@b.com");

Console.WriteLine(u1 == u2);       // True (值相等)
Console.WriteLine(ReferenceEquals(u1, u2)); // False (地址不同)

B. 非破坏性修改(with 表达式)

由于 record 默认是不可变的(Immutable),你不能直接修改 u1.Name = "李四"。如果你想改名,你需要创建一个“克隆体”。

// 创建一个新对象,除了 Name 变了,其他都拷贝自 u1
var u3 = u1 with { Name = "李四" };

var person1 = new Person("Nancy", "Davolio");
var person2 = person1 with { FirstName = "John" };

C. 继承能力

record 支持继承,这是它比 structTuple 强大的地方。

public record Manager(int Id, string Name, string Email, string Department)
    : User(Id, Name, Email);

3. Record 的两种形态

虽然默认 record 是引用类型(record class),但 C# 10 引入了值类型版本:

  • record class(或直接写 record):存放在堆上。适合大型数据模型、实体。
  • record struct:存放在上。结合了 struct 的性能和 record 的语法便利。注意: record struct 默认是可变的(属性是 set 而不是 init),除非你加 readonly

内存布局对比

类型声明 本质 存储位置 赋值行为
record 引用类型 (Reference Type) 堆 (Heap) 复制的是地址(指针)。两个变量指向同一个对象。
record struct 值类型 (Value Type) 栈 (Stack) 复制的是完整的数据内容。两个变量完全独立。

4. 选型指南:什么时候用什么?

需求场景 推荐方案
需要修改属性值、需要复杂的生命周期管理 class
仅仅是临时的多个返回值、不需要名字或名字不重要 ValueTuple
定义数据库实体、DTO、API 传输对象、且希望对象不可变 record
高性能、极小的数据包(如坐标点)、且希望按值比较 record struct

Record vs Class vs ValueTuple

特性 Class ValueTuple Record
类型属性 引用类型 (Heap) 值类型 (Stack) 引用类型 (Heap)
相等性判断 引用相等(比地址) 值相等(比内容) 值相等(比内容)
生命周期 长期存在,适合复杂业务 瞬时存在,适合局部传递 长期存在,适合作为数据模型
封装性 强,支持私有成员 无,字段全公开 强,支持复杂逻辑
概念 逻辑归属 理由
Record Types / Classes 它定义了“是什么”。它是引用类型,具备面向对象的继承特性。
Tuple Types / Structs 它也定义了“是什么”。虽然是轻量级的,但它在内存中是明确的结构体。
ref / out Methods / Parameters 它们定义了“怎么传”。它们属于方法调用约定的一部分。

运行机制流程

在这里插入图片描述


术语表

  • Init-Only Setter:一种属性访问器。它只允许在对象初始化的那一刻赋值(在构造函数或对象初始化器中),赋值后对象就成了“只读”状态。
  • 非破坏性修改 (Non-destructive mutation):不改变原对象,而是通过复制原对象并修改指定部分来创建新对象。
  • 解构 (Deconstruction):将对象拆解为多个变量的语法。
  • 位置参数 (Positional Parameters):在类名后面括号里定义的参数,编译器会自动将其转换为对应的属性。
  • 堆 (Heap)用于存储长寿对象。由 GC(垃圾回收器)负责清理。访问速度相对栈慢一点点(因为要根据指针去找地址)
  • 栈 (Stack)用于存储局部变量。随着方法调用结束自动释放,速度极快
  • 引用类型 (Reference Type):变量本身只存一个“内存地址”。
  • 值类型 (Value Type):变量直接包含实际的数据。
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐