with表达式仅支持record类型及显式实现IEquatable且字段全为init/只读的类型;普通class不支持,即使字段设为readonly或init;其创建的是浅拷贝,嵌套引用类型需手动用with更新。

with表达式只能用于record和不可变类型
不是所有类都能用 with,只有 record(含 record struct)或显式实现 IEquatable 且字段全为 init 或只读的类型才支持。普通 class 即使字段都设为 readonly 或 init,也不行——编译器不认。
常见错误:CS8957: 'with' expression cannot be applied to type 'MyClass',说明类型没被识别为可 with 的 record。
- 必须用
record关键字声明,例如:record Person(string Name, int Age); - 继承自另一个 record 的子 record 默认也支持
with - 如果用了
record class并手动加了构造函数或属性 setter,要确保所有字段/属性仍通过init初始化
with 创建的是浅拷贝,嵌套 record 需手动处理
with 表达式默认只复制顶层字段,对引用类型字段(比如另一个 record、List、string)不做深拷贝。这意味着修改嵌套对象会影响原实例。
示例:
record Address(string City);
record Person(string Name, Address Home);
var p1 = new Person("Alice", new Address("Beijing"));
var p2 = p1 with { Name = "Bob" };
// ✅ p2.Name 是新值
// ❌ p2.Home 和 p1.Home 指向同一个 Address 实例- 若需更新嵌套 record 字段,必须显式调用其
with:p1 with { Home = p1.Home with { City = "Shanghai" } } - 对集合类(如
List),with不会复制内容,需用new List或 LINQ 的(oldList) ToList() - 字符串是不可变引用类型,所以
with中改string字段安全,无需额外操作
init-only 属性与 with 的配合要点
record 的字段自动转为 init 属性,但如果你手动定义属性,必须显式用 init(不能用 set),否则 with 无法赋值该字段,编译报错 CS8958: Property 'X' cannot be assigned in 'with' expression。
- 正确写法:
public string Name { get; init; } = default!; - 错误写法:
public string Name { get; set; }(允许后续修改,破坏不可变语义) - 带私有 backing field 的 init 属性也支持,但需确保 getter 可访问,且无逻辑副作用
- 如果 record 含
object类型字段,with能赋值,但运行时类型检查靠你自己保证
性能和兼容性注意点
with 表达式在编译期生成新实例,底层调用的是 record 的拷贝构造函数(compiler-generated copy constructor),不是反射也不是序列化,所以性能接近手写 new + 赋值。
- .NET 5+ 才完全支持
with;.NET Core 3.1 只支持 C# 9 的 record 基础语法,但不支持with表达式 - 泛型 record 如
record Result,(T Value, bool Success) with可正常工作,类型推导准确 - 调试时注意:IDE 可能显示两个 record 实例的 HashCode 相同(因为 record 默认重写
GetHashCode基于值),但这不代表它们是同一对象
真正容易漏掉的是嵌套引用类型的处理——看起来一行 with 很干净,实际可能留下共享状态,尤其在并发或长期持有副本的场景下。










