C# with表达式使用方法 C#如何创建Record的非破坏性副本

来源:这里教程网 时间:2026-02-21 17:41:03 作者:

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
很干净,实际可能留下共享状态,尤其在并发或长期持有副本的场景下。

相关推荐