C# 9.0引入了record类型,它和传统的class类型同属引用类型,但在设计理念和特性上有诸多不同,下面从多个维度展开对比说明。

基础定义差异
class是C#传统的引用类型,定义时需要显式声明构造函数、属性等成员,而record提供了更简洁的语法,支持使用位置参数快速定义。
首先是class的典型定义方式:
// class类型定义
public class PersonClass
{
public string Name { get; set; }
public int Age { get; set; }
public PersonClass(string name, int age)
{
Name = name;
Age = age;
}
}
对应的record定义可以非常简洁:
// record类型定义,使用位置参数自动生成属性、构造函数等 public record PersonRecord(string Name, int Age);
相等性判断逻辑不同
这是两者最核心的差异之一。class默认使用引用相等性,即两个对象只有指向同一个堆内存地址时才判定为相等;而record默认使用值相等性,只要两个对象的所有属性值都相同,就判定为相等。
我们可以通过以下代码验证这个差异:
PersonClass class1 = new PersonClass("张三", 20);
PersonClass class2 = new PersonClass("张三", 20);
// class的引用相等性判断,结果为false
Console.WriteLine(class1 == class2);
PersonRecord record1 = new PersonRecord("张三", 20);
PersonRecord record2 = new PersonRecord("张三", 20);
// record的值相等性判断,结果为true
Console.WriteLine(record1 == record2);
如果需要让class也支持值相等性,需要手动重写Equals方法、GetHashCode方法,还要重载==和!=运算符,实现成本远高于record。
不可变性支持差异
record天然支持不可变性,默认生成的属性是只读的,初始化后无法修改属性值;而class的属性默认是可写的,除非显式将set访问器设为private或者只保留get访问器。
尝试修改record的属性会直接编译报错:
PersonRecord record = new PersonRecord("李四", 25);
// 编译错误:属性是只读的,无法赋值
record.Name = "王五";
如果需要在record中修改属性,可以使用with表达式生成新的record实例,原实例不会被修改:
PersonRecord record1 = new PersonRecord("李四", 25);
// 生成新的record实例,修改Name属性
PersonRecord record2 = record1 with { Name = "王五" };
Console.WriteLine(record1.Name); // 输出 李四,原实例未修改
Console.WriteLine(record2.Name); // 输出 王五
而class要修改属性则非常直接,不需要生成新实例:
PersonClass person = new PersonClass("李四", 25);
person.Name = "王五"; // 直接修改属性,编译通过
Console.WriteLine(person.Name); // 输出 王五
内置方法差异
record会自动生成一些class默认没有的方法:
ToString方法:record重写了ToString,会输出所有属性名和对应的值,方便调试;class的默认ToString输出的是类型全名。Deconstruct方法:record自动支持解构,可以直接将属性拆分到多个变量中。
以下是相关用法的代码示例:
PersonRecord record = new PersonRecord("赵六", 30);
// record的ToString输出:PersonRecord { Name = 赵六, Age = 30 }
Console.WriteLine(record.ToString());
// record的解构用法
var (name, age) = record;
Console.WriteLine($"姓名:{name},年龄:{age}"); // 输出 姓名:赵六,年龄:30
PersonClass person = new PersonClass("赵六", 30);
// class的ToString输出:PersonClass(实际会带命名空间)
Console.WriteLine(person.ToString());
适用场景对比
根据两者的特性,适用场景也有明显区分:
| 类型 | 适用场景 |
|---|---|
| class | 需要灵活控制对象行为、需要频繁修改属性值、需要继承扩展复杂逻辑的场景,比如业务实体、服务类、包含复杂方法的对象等。 |
| record | 只需要承载数据、不需要修改属性值、需要值相等性判断的场景,比如DTO、配置模型、临时数据载体、不可变数据对象等。 |
继承特性差异
record支持继承,但是只能继承自其他record,不能继承自class;class可以继承自其他class,也可以实现接口,record同样可以实现接口。
// record的继承示例
public record AnimalRecord(string Name);
public record DogRecord(string Name, string Breed) : AnimalRecord(Name);
// record实现接口示例
public interface IHasId
{
int Id { get; }
}
public record UserRecord(int Id, string Name) : IHasId;
需要注意的是,record的继承场景下,值相等性判断会同时比较基类和派生类的所有属性,只有全部属性值都相同才会判定为相等。