在C#9.0及更高版本的.NET生态中,Record类型是一种专门面向不可变数据场景设计的引用类型,它从语法层面简化了不可变数据模型的构建过程,避免了传统类实现不可变属性时需要编写的大量重复代码。Record类型默认将属性设置为不可变,同时内置了值相等性判断、解构、友好的字符串输出等常用功能,让开发者可以用极简的语法完成不可变数据类型的开发。

Record类型的基础定义
Record类型的定义语法非常简洁,最常见的形式是使用位置参数定义,编译器会自动生成对应的不可变属性和构造函数。示例如下:
// 定义包含姓名和年龄的Person Record
public record Person(string Name, int Age);
// 使用位置参数创建实例
Person person1 = new Person("张三", 25);
// 也可以显式指定属性名创建
Person person2 = new Person(Name: "李四", Age: 30);
上述代码定义了一个Person Record,包含两个不可变属性Name和Age。编译器会自动生成以下内容:
- 两个只读的init-only属性Name和Age
- 包含对应参数的构造函数
- 基于所有属性的Equals和GetHashCode方法
- 重写的ToString方法,输出格式化的属性信息
- 解构方法,支持将实例的属性拆分到变量中
Record类型的核心特性
不可变性
Record类型的属性默认是init访问器,只能在对象初始化时赋值,后续无法修改。如果尝试修改属性值,编译器会直接报错:
Person person = new Person("张三", 25);
// 以下代码会编译报错,因为Age是init-only属性
// person.Age = 26;
值相等性
普通类的相等性判断默认是引用相等,即两个对象指向同一个内存地址才判定为相等。而Record类型默认实现值相等性,只要两个实例的所有属性值都相同,就判定为相等:
Person p1 = new Person("张三", 25);
Person p2 = new Person("张三", 25);
// 输出True,因为值相等
Console.WriteLine(p1 == p2);
// 普通类的话这里会输出False
with表达式
Record类型支持with表达式,可以基于现有实例创建一个新实例,仅修改指定属性的值,其他属性保持原值,非常适合不可变数据的浅拷贝场景:
Person person = new Person("张三", 25);
// 创建新实例,年龄改为26,姓名保持不变
Person newPerson = person with { Age = 26 };
Console.WriteLine(newPerson);
// 输出 Person { Name = 张三, Age = 26 }
解构支持
Record类型自动生成解构方法,可以直接将实例的属性拆分到对应变量中:
Person person = new Person("张三", 25);
// 解构实例,获取Name和Age
(string name, int age) = person;
Console.WriteLine($"姓名:{name},年龄:{age}");
// 输出 姓名:张三,年龄:25
Record类型与普通类的区别
虽然Record本质是引用类型,但和普通类有诸多不同,我们可以通过下表清晰对比两者的差异:
| 对比维度 | 普通类 | Record类型 |
|---|---|---|
| 属性默认可变性 | 默认可读可写 | 默认init-only,不可变 |
| 相等性判断 | 引用相等 | 值相等 |
| ToString方法 | 输出类型全名 | 输出格式化的属性键值对 |
| 样板代码量 | 实现不可变需要手动写大量代码 | 语法简洁,自动生成常用方法 |
| with表达式支持 | 不支持 | 原生支持 |
Record类型的适用场景
Record类型非常适合以下场景:
- DTO(数据传输对象),尤其是API接口的请求和响应模型
- 领域模型中的值对象,比如订单号、金额、地址等不可变概念
- 临时数据容器,需要频繁创建、比较、传递的场景
- 需要值相等性判断的引用类型场景
进阶用法:自定义Record
如果需要添加自定义方法或者修改默认行为,也可以像普通类一样扩展Record类型:
public record Person(string Name, int Age)
{
// 添加自定义方法
public string GetDescription()
{
return $"{Name}今年{Age}岁";
}
// 重写默认的ToString方法
public sealed override string ToString()
{
return $"人员信息:姓名{Name},年龄{Age}";
}
}
// 使用自定义Record
Person person = new Person("张三", 25);
Console.WriteLine(person.GetDescription());
// 输出 张三今年25岁
Console.WriteLine(person);
// 输出 人员信息:姓名张三,年龄25
需要注意的是,Record类型支持继承,但是只能继承自其他Record类型,不能继承自普通类,普通类也不能继承自Record类型。例如可以定义Student Record继承自Person Record:
public record Student(string Name, int Age, string School) : Person(Name, Age);
Student student = new Student("张三", 25, "XX大学");
Console.WriteLine(student);
// 输出 Student { Name = 张三, Age = 25, School = XX大学 }
总的来说,Record类型是.NET中编写不可变数据模型的高效工具,它平衡了代码的简洁性和功能的完备性,在合适的场景下使用可以大幅减少重复代码,提升开发效率。