record是C# 9.0引入的不可变数据载体,用record关键字声明,自动实现值相等性、ToString、GetHashCode和with表达式;class需手动实现这些,且默认引用相等。

Record类型怎么写,和class写法差在哪
Record类型是C# 9.0起引入的不可变数据载体,语法上用record关键字声明,核心差异在于编译器自动生成值相等性、ToString()、GetHashCode()和With表达式支持——这些class默认都不带。
最简写法:
public record Person(string Name, int Age);
这行代码等价于手写一个含两个只读属性、按字段值比较相等、能with构造新实例的类。而同样结构的class需要手动实现Equals、重载==、写ToString,还容易漏掉GetHashCode一致性校验。
-
record默认所有参数对应属性是init(仅初始化时可赋值),不是get;set; - 不能用
new Person { Name = "a" }这种对象初始化器(除非显式定义set或init属性) - 继承
record需用record关键字,不能用class继承record
什么时候该用record而不是class
适合用record的场景非常明确:你定义的是纯数据容器,不封装行为,且希望“内容相同即相等”。典型如DTO、API响应模型、配置快照、事件载荷。
- Web API返回
record Product(string Id, string Name, decimal Price):前端按内容比对是否更新,不用自己写Equals - 函数式风格处理数据流,比如LINQ中
.Distinct()自动按字段去重 - 做状态快照(如UI状态、游戏帧数据),用
with创建差异副本:current with { Count = current.Count + 1 }
不适合的场景:需要频繁修改字段、有复杂业务逻辑、依赖引用相等性(比如缓存键用ReferenceEquals)、或必须兼容.NET Framework(record最低要求.NET 5 / .NET Core 3.1)。
常见错误:以为record完全不可变,结果字段还能改
record只保证**位置参数生成的属性是init**,不阻止你在内部定义可变字段或属性。下面这段代码合法但违背record本意:
public record BadExample(string Name) { public int Counter { get; set; } = 0; }
-
Counter可被任意修改,破坏了值语义——两个BadExample实例Name相同但Counter不同,Equals仍返回true(因为只比较Name) - 若真需要可变状态,直接用
class更清晰;若想部分字段参与相等判断,把它们加进record构造参数里 - 调试时注意:VS调试器显示
record变量值会调用PrintMembers,但如果你重写了ToString,它可能不显示全部字段
性能和兼容性要注意的点
Record的Equals和GetHashCode是编译器生成的,对小字段数性能接近手写,但字段超10个以上可能略慢于手工优化的class实现(不过多数场景无感)。真正要留意的是序列化和反序列化。
- System.Text.Json默认支持
record,但Newtonsoft.Json(Json.NET)低于13.0.1版本不支持位置记录(positional record)的构造函数反序列化,会报Could not create an instance of type X. Type is an interface or abstract class - EF Core 6+支持
record映射到数据库,但不能用作实体(因缺少无参构造函数和可变属性),只能当查询结果类型(Select投影目标) - 反射获取
record字段时,位置参数对应的是InitOnly属性,PropertyInfo.CanWrite为false,但FieldInfo.IsInitOnly才是准确判断依据
record不是class的替代品,它是针对“数据即值”这一特定抽象的语法糖。用错地方反而让意图模糊——比如把订单服务的OrderService写成record,编译器不会拦你,但团队看到就会困惑。











