从翻车到「版本答案」
2026/6/26 2:48:30 网站建设 项目流程

前言

几年前写过一个 bug,根因很土:该深拷贝的地方没深拷贝,副本一改,原件跟着变。排查的时候老板以为动的是库里的数据,其实就是一个本地对象被共享了。

先把词说清楚:

浅拷贝:值类型复制一份;引用类型复制的是引用,两边还指着同一个子对象。你改副本里的引用成员,原件也会变。

只复制对象自身的一层:字段/属性里如果是值类型,会复制一份值;如果是引用类型,复制的是引用(指针),新旧对象仍指向同一块堆上的子对象。

深拷贝:引用链上也建新对象,改副本不该动到原件的嵌套数据。

从根对象开始,递归地为引用类型也创建新实例,并把内容复制过去,直到整棵「对象图」在逻辑上独立。改拷贝不应意外改动原对象里的嵌套数据。


ICloneable:能深,但接口不保证

ICloneable只有一个object Clone(),文档不会替你承诺浅还是深,看实现。你想做深拷贝,可以,全写在Clone()里就行。

浅拷贝场景下,改拷贝里的引用类型字段,往往会影响原对象(反之亦然),除非你再给那个字段赋一个新实例。

// 浅拷贝示例(Address 还是同一个引用) public class DeepAndShallowCopy { public static void ShallowCopy() { var rawUser = new UserDto { Id = 1, Name = "name1", Address = new UserDto.AddressDto { City = "CS" } }; var copyUser = rawUser.Clone() as UserDto; copyUser.Id = 2; copyUser.Name = "name2"; copyUser.Address.City = "CS2"; // 浅拷贝:动的是同一块 Address,原数据跟着变 Console.WriteLine($"rawUser={JsonSerializer.Serialize(rawUser)}"); Console.WriteLine($"copyUser={JsonSerializer.Serialize(copyUser)}"); } } public class UserDto : ICloneable { public int Id { get; set; } public string Name { get; set; } public AddressDto Address { get; set; } public object Clone() { return new UserDto { Id = Id, Name = Name, Address = Address }; } public class AddressDto { public string City { get; set; } } }

深拷贝就要让AddressClone()一份。引用类型多就一层层写,啰嗦但清楚。

public class DeepAndShallowCopy { public static void DeepCopy() { var rawUser = new UserDto { Id = 1, Name = "name1", Address = new UserDto.AddressDto { City = "CS" } }; var copyUser = rawUser.Clone() as UserDto; copyUser.Id = 2; copyUser.Name = "name2"; copyUser.Address.City = "CS2"; // 深拷贝:Address 已是新实例,原数据不变 Console.WriteLine($"rawUser={JsonSerializer.Serialize(rawUser)}"); Console.WriteLine($"copyUser={JsonSerializer.Serialize(copyUser)}"); } } public class UserDto : ICloneable { public int Id { get; set; } public string Name { get; set; } public AddressDto Address { get; set; } public object Clone() { return new UserDto { Id = Id, Name = Name, Address = Address.Clone() as AddressDto }; } public class AddressDto : ICloneable { public string City { get; set; } public object Clone() { return new AddressDto { City = City }; } } }

手写这条路:性能好,行为自己说了算。代价是对象图一大就容易漏,漏一处就是浅拷贝;另外Clone()返回object,调用处总要转一下类型,有点烦。


序列化 / AutoMapper:省事,但要心里有数

我们 CRUD 程序员经常不想维护一整张克隆图,就会想走捷径。

System.Text.Json

思路就是序列化再反序列化,得到一棵新对象。代码少,DTO、配置这类能完整序列化的类型用起来很省事。

public class DeepAndShallowCopy

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询