record 是编译器强制注入不可变语义和值相等性的数据容器,非更短的 class;默认为 record class(引用类型),需值语义则显式声明 record struct;with 仅浅拷贝,ef core 中不可直接作实体,仅宜作 dto 或投影。

record 不是“更短的 class”,它是编译器强制注入不可变语义和值相等性的数据容器——用错场景或忽略 init 语义,很快会遇到编译报错、with 失效、EF Core 拒绝保存等问题。
record 声明时要不要写 class 或 struct?
必须明确:record 本身只是修饰符,不是独立类型。不写 class 或 struct 时,默认是 record class(引用类型);需要值语义或栈分配,得显式写 record struct。
-
public record Person(string Name, int Age);→ 等价于public record class Person(...),值比较、堆上分配 -
public record struct Point(double X, double Y);→ 值类型,复制开销小,适合坐标、枚举包装等轻量结构 -
public readonly record struct Temperature(double C);→ 强制只读 + 值类型,避免内部字段被意外修改
混用 record 和无修饰的 class 定义,在泛型约束中容易触发 T must be a non-abstract type 错误。
with 表达式为什么改了副本,原对象也跟着变?
with 只做浅拷贝:它新建 record 实例,但所有引用类型字段(比如 List<string></string>、Dictionary<int object></int>)仍指向原对象。这不是 bug,是设计使然。
- ❌ 错误假设:
var p1 = new Person("A", new List<string> { "x" }); var p2 = p1 with { Name = "B" }; p2.Tags.Add("y"); // p1.Tags 也变!</string> - ✅ 正确做法:用只读集合接口 + 构造时新实例化:
public record Person(string Name, IReadOnlyList<string> Tags) { public Person(string name, IEnumerable<string> tags) : this(name, tags.ToList().AsReadOnly()) { } }</string></string> - 别依赖
with处理含可变状态的子对象——它不是深拷贝魔法,只是编译器生成的字段级赋值
record 能不能直接当 EF Core 实体用?
不能。EF Core 要求实体具备引用相等性(同一数据库行必须映射为同一个 .NET 对象实例),而 record 默认启用值相等性,且属性是 init(无法被 EF 的 change tracker 修改)。
- 典型报错:
InvalidOperationException: The property 'Name' has no setter - 替代方案:用普通
class做实体,用record做 DTO 或查询投影:context.People.Select(p => new Person(p.Name, p.Age)) - 如果硬要用 record 接收查询结果,确保不把它传回
DbSet<t></t>进行SaveChanges()
最常被忽略的一点:record 的不可变性只作用于自身字段赋值,不递归冻结内容。哪怕你用了 readonly record struct,只要字段是 List<t></t>,里面的数据照样能被改——这和“逻辑不可变”不是一回事。











