只有 record 类型(含 record class 和 record struct)支持 with 表达式,普通 class 或 struct 会报 cs8852 错误;with 是浅拷贝,引用类型仅复制地址,嵌套可变对象(如 list)会被共享。

with 表达式只对 record 类型(含 record class 和 record struct)有效,普通 class 或 struct 直接使用会编译报错 CS8852。
哪些类型能用 with?必须是 record
不是写了 init 属性就能用 with,也不是继承了 record 的子类就自动支持——它依赖编译器为 record 自动生成的克隆逻辑。
-
public record Person(string Name, int Age);✅ 支持 -
public record struct Point(int X, int Y);✅ 同样支持 -
public class Person { public string Name { get; init; } }❌ 编译失败,报CS8852 -
public record Base(string Id); public class Derived : Base { ... }❌Derived不是record,不能用with
with 是浅拷贝,不是深拷贝
它复制字段值:值类型直接拷贝,引用类型只拷贝地址。这意味着原始对象和新对象共享嵌套的可变引用。
- 如果
record里有public List<string> Tags { get; init; }</string>,那么newObj = oldObj with { }后,newObj.Tags和oldObj.Tags指向同一个List - 后续调用
newObj.Tags.Add("x"),oldObj.Tags也会看到这个新增项 - 想规避?把字段换成不可变类型,比如
IReadOnlyList<string></string>或ImmutableArray<string></string> - 别指望
with自动帮你“冻结”嵌套结构——它只管顶层字段
在 init setter 里调用 with 很危险
容易触发无限递归,编译器不拦,运行时直接栈溢出。
- 常见错误模式:
public string Name { get; init; } = Name switch { null => throw ..., _ => Name };+public Person WithName(string name) => this with { Name = name }; - 如果
initsetter 内部又间接调用了WithName(比如校验失败重试),就会死循环 - 安全做法:避免在
initsetter 中调用任何可能间接触发with的方法 - 更稳妥的替代:用构造函数显式创建新实例,绕过
with链路
with 赋值对 null 字段照常生效
哪怕某个字段当前是 null,with 依然会把它设成新值,不会跳过、也不会抛空引用异常。
- 例如:
var p = new Person(null, 25); var p2 = p with { Name = "Alice" };—— 合法且无异常 - 但要注意:如果字段类型本身不允许
null(如string!或启用了可为空引用类型检查),编译器可能提前警告 - 这和对象初始化器行为一致,但和某些深拷贝工具的“跳过 null”策略不同











