尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
C#如何优雅处理引用类型的深拷贝
年前写过一个 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; } } }深拷贝就要让Address也Clone()一份。引用类型多就一层层写啰嗦但清楚。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 { public static void DeepCopyByJsonSerializer() { var rawUser new UserDto { Id 1, Name name1, Address new UserDto.AddressDto { City CS } }; var copyUser rawUser.DeepCopy(); copyUser.Id 2; copyUser.Name name2; copyUser.Address.City CS2; Console.WriteLine($rawUser{JsonSerializer.Serialize(rawUser)}); Console.WriteLine($copyUser{JsonSerializer.Serialize(copyUser)}); } } public class UserDto { public int Id { get; set; } public string Name { get; set; } public AddressDto Address { get; set; } /// summary /// 序列化 → 反序列化换一批新实例 /// /summary public UserDto DeepCopy() { var rawUserString JsonSerializer.Serialize(this); return JsonSerializer.DeserializeUserDto(rawUserString)!; } public class AddressDto { public string City { get; set; } } }好处是加字段一般不用改克隆逻辑只要还能序列化。麻烦在循环引用要单独配委托、怪类型、非公开成员也可能过不去。AutoMapperpublic static void DeepCopyByAutoMapper() { var config new MapperConfiguration(config { config.CreateMapUserDto, UserDto(); // 子类型也要建同型映射否则 Address 可能还是同一条引用 config.CreateMapUserDto.AddressDto, UserDto.AddressDto(); }); IMapper mapper config.CreateMapper(); var rawUser new UserDto { Id 1, Name name1, Address new UserDto.AddressDto { City CS } }; var copyUser mapper.MapUserDto(rawUser); copyUser.Id 2; copyUser.Name name2; copyUser.Address.City CS2; Console.WriteLine($rawUser{JsonSerializer.Serialize(rawUser)}); Console.WriteLine($copyUser{JsonSerializer.Serialize(copyUser)}); }项目里本来就有 Mapper 的话顺手Map一下也行。它本职是 DTO 映射不是克隆库子图没配齐、策略不对照样可能浅拷贝。别指望「默认就是深拷贝」。JSON 和 Mapper 本质上都是在「按数据重建对象」只是经常能重建出一棵独立的树和手写Clone的语义不是一回事。record我眼里的版本答案前面ICloneable要到处asJSON 像绕路AutoMapper 容易配成玄学。record从语言层面把「数据」这件事说清楚了默认值语义、相等性、ToString、非破坏性修改编译器帮你生成一大坨样板你只要在业务代码里写with。为什么说它像版本答案不是银弹但在「数据拷贝 / 派生」这条线上很对味相等按值比同类型的两个实例成员一样就相等写单元测试、去重、缓存 key 都省心。class 默认比引用想比内容要自己重写Equals/GetHashCode一懒就埋雷。with是语法级的「从旧副本改几处」读代码的人一眼知道「基于 rawUser 出了一个新对象」不用跳进Clone()里猜深还是浅。打印友好自动生成的ToString把主要字段打出来日志里好认排障少猜几次。语法上有两种常见写法知道就行传统属性写法和 class 差不多只是类型是record白嫖相等性和with。位置参数 recordpublic record UserDto(int Id, string Name, AddressDto Address);编译器帮你生成主构造函数、解构、with里按位置对应DTO 里很省字。默认的record是引用类型相当于record class。还有record struct那是值类型语义拷贝整坨 struct 时是按位复制和「引用图里拆不拆」又是另一套题别混在一块讲深拷贝时搞晕自己就行。with和深拷贝的关系再强调一遍避免面试翻车with会复制你没改到的成员引用类型的成员如果没在with里换掉新旧两边仍指着同一个子对象。所以要深就显式写Address rawUser.Address with { ... }或给一个新的实例。嵌套深就链式with丑一点但诚实——至少「哪里拆引用」全摊在调用点不靠隐式魔法。想往「真·快照」靠可以把属性收成init或只在构造函数里赋值外面用with派生。{ get; set; }照样能改字段别嘴上说 record 不可变、手还在到处 set。和class分工可以这样记class 扛行为、生命周期长、引用身份有时就是业务含义record 扛可比较的数据快照、适合命令/事件/读模型里那种「从上一版捏一版」的写法。public class DeepAndShallowCopy { public static void DeepCopyByRecord() { var rawUser new UserDto { Id 1, Name name1, Address new UserDto.AddressDto { City CS } }; var copyUser rawUser with { Id 2, Name name2, Address rawUser.Address with { City CS2 } }; Console.WriteLine($rawUser{JsonSerializer.Serialize(rawUser)}); Console.WriteLine($copyUser{JsonSerializer.Serialize(copyUser)}); } } //注意这不是标准用法只是为了演示 public record UserDto { public int Id { get; set; } public string Name { get; set; } public AddressDto Address { get; set; } public record AddressDto { public string City { get; set; } } }标准写法示意省样板相等/ToString/with照样有public record AddressDto(string City); public record UserDto(int Id, string Name, AddressDto Address); var rawUser new UserDto(1, name1, new AddressDto(CS)); var copyUser rawUser with { Name name2, Address rawUser.Address with { City CS2 } };怎么选没有银弹按常见情况排个序够用就行核心模型、性能敏感、要一眼能审代码手写Clone或工厂从旧对象构造新的。普通 DTO / 配置、能序列化、没环JSON 往返最省事。项目里 Mapper 已经到处都是可以CreateMapT,T()把子类型也配全并记得做配置校验和用例别光靠手感。业务就是「从旧状态派生一个新状态」、嵌套也愿意写清楚recordwith。
RELATED

相关推荐

TLA2518与TM4C129ENCPDT构建高精度信号采集系统

TLA2518与TM4C129ENCPDT构建高精度信号采集系统

1. 为什么需要高精度模拟信号采集?在工业自动化、医疗设备和消费电子等领域,我们经常需要将现实世界中的模拟信号(如温度、压力、声音等)转换为数字信号进行处理。这种转换的精度和可靠性直接决定了整个系统的性能表现。以医疗监护…

📅 2026/9/17 15:55:13
X射线聚焦系统

X射线聚焦系统

高能光子(X射线)的使用已成为许多医疗和同步加速器应用的共同特点。与可见光谱中的光不同,X射线与大多数物质仅发生微弱的相互作用,这使得聚焦元件的设计比波长谱的其他部分更具挑战性。下面我们展示了两种解决此任务的方法,即使用复合透镜和…

📅 2026/10/12 14:38:22
【华硕无畏X1405V在ubuntu22.04下无线网卡修复】

【华硕无畏X1405V在ubuntu22.04下无线网卡修复】

华硕无畏X1405V在ubuntu22.04下无线网卡修复 wifi型号:MT7902 lspci | grep -i network 0000:02:00.0 Network controller: MEDIATEK Corp. Device 7902修复前提:使用usb-网口连上有线网 修复方法:下载codex,让他帮我修 遇到的几个…

📅 2026/10/19 18:09:57
MORE NEWS

更多资讯

📰

东莞高埗电工证复审实操怕挂科?掌握这些技巧一次过

东莞高埗电工证复审实操怕挂科?掌握这些技巧一次过 实操考试心里没底怕挂科,复审流程又复杂,尤其是像东莞高埗这种地方,考证门槛不低,但证书含金量高, 电子证书…

📰

低压电工证夜间培训含金量高吗?新手怕被中介坑怎么避坑

低压电工证夜间培训含金量高吗?新手怕被中介坑怎么避坑 你是不是也遇到过这种情况,想考低压电工证,但不知道去哪报名,担心被中介坑钱,耽误时间?其实,低压电工证夜间培训的含金量并不低,尤其在南阳这类劳务大市,持证上岗已经是基本门槛,但真正懂行的…

📰

旧宫电工证报名地点怎么考 你的证全国通用吗

旧宫电工证报名地点怎么考 你的证全国通用吗 之前考的证在外省能不能转过来?这是很多电工朋友最关心的问题。尤其是在旧宫地区,不少电工是从外地过来打工的,手里拿着老家的电工证,却不知道在本地能不能用。好消息是, 全国通用…

📰

邵阳电工证哪里考2026河南最新政策全攻略

邵阳电工证哪里考2026河南最新政策全攻略 证书过期了不知道怎么复审延期,这个问题在电工行业里再常见不过。很多人在工作几年后才发现,电工证不是一劳永逸的, 2026河南最新政策…

📰

杭州新能源汽车电工证真实经历:工地太忙根本没时间复习考试怎么办?

杭州新能源汽车电工证真实经历:工地太忙根本没时间复习考试怎么办? 工地太忙根本没时间复习考试,这是我去年在杭州做新能源汽车电工证考试时的真实经历。作为一个从信阳转岗过来的从业者,我深知这种焦虑感有多强烈。很多人在一线岗位上,每天忙得连吃饭的…

📰

重庆电工证年审地址怎么找?外省证全国通用吗?

重庆电工证年审地址怎么找?外省证全国通用吗? 之前考的证在外省能不能转过来?很多小伙伴在考电工证时,可能因为工作地点变动、考试地点不一致,甚至不清楚如何办理年审,导致证书“失效”。别担心, 重庆电工证年审地址…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬