c#实现原型模式应优先用record with,真需克隆则必须手写clone()而非依赖memberwiseclone()——因后者仅浅拷贝,会导致原对象与副本共享引用类型实例而引发隐蔽数据污染。

直接说结论:C# 里实现原型模式,别硬套 ICloneable,优先用 record with;真要克隆,必须自己写 Clone(),不能只靠 MemberwiseClone()——否则改副本会悄悄改原对象。
为什么 MemberwiseClone() 不是“能用就行”的克隆
MemberwiseClone() 是 object 类的受保护方法,它只做浅拷贝:值类型字段复制值,引用类型字段只复制地址。这意味着两个对象共享同一份 List<string></string>、同一个 Address 实例、甚至同一个 FileStream。
- 常见错误现象:
clone.Items.Add("new")后,original.Items也多了一项 -
clone.Weapon.Durability--导致原对象武器耐久同步下降 - 文档编辑器 Undo 时,上一版的图片列表被当前编辑污染
这不是 bug,是设计行为。它适用的场景极窄:类完全由值类型组成,或你明确希望共享内部状态(比如共用一个 ConcurrentDictionary)。
record with 表达式替代传统克隆
C# 9+ 的 record 天然适合原型模式的“基于旧值构造新值”语义,且编译器保证字段级不可变性(除非你显式声明 public set 或可变引用字段)。
- 使用条件:模型结构稳定、适合不可变语义(如 DTO、配置快照、事件载荷)
- 不依赖
ICloneable接口契约(比如第三方库强制要求传ICloneable) -
with不等于深拷贝:若字段是List<string></string>,仍需手动新建:original with { Tags = new(original.Tags) }
示例:var copy = original with { Name = "Bob" }; —— 这本质是编译器生成的新实例构造调用,安全但不自动隔离引用。
需要真正深拷贝时怎么写 Clone()
当必须返回 ICloneable、类型无法改造成 record、或含不可序列化资源(如 Socket、FileStream)时,就得手写深拷贝逻辑。
- 每个引用类型字段都要显式新建实例并填充内容:
new List<t>(source)</t>、source.Select(x => x.Clone()).ToList() - 嵌套对象必须递归调用其
Clone()方法,不能漏层级 - 避免用
JsonSerializer.Serialize/Deserialize处理含循环引用、非public字段、或[JsonIgnore]的类型 - 别用
BinaryFormatter(已过时),它要求[Serializable]且不支持跨 .NET 版本反序列化
示例中 Person 克隆 Address 字段时,必须显式调用 this.Address.Clone(),否则仍是浅拷贝。
最易被忽略的点:即使用了 record,只要字段是可变引用类型(如 public List<string> Items</string>),with 就不会帮你隔离;而手写 Clone() 时,漏掉任意一层嵌套引用,就会在运行时悄悄污染原对象。










