在 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

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

运行机制流程

在这里插入图片描述


术语表

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

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

更多推荐