with表达式只能用于record和不可变类型
不是所有类都能用
with,只有
record(含
record struct)或显式实现
IEquatable<t></t>且字段全为
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<t></t>、
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<string></string>),
with不会复制内容,需用
new List<string>(oldList)</string>或 LINQ 的
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>(T Value, bool Success)</t>,
with可正常工作,类型推导准确 调试时注意:IDE 可能显示两个 record 实例的 HashCode 相同(因为 record 默认重写
GetHashCode基于值),但这不代表它们是同一对象
真正容易漏掉的是嵌套引用类型的处理——看起来一行
with很干净,实际可能留下共享状态,尤其在并发或长期持有副本的场景下。
