.NET 数据拷贝方案选择

news/2025/1/14 23:50:28/文章来源:https://www.cnblogs.com/kybs0/p/18669035

 应用中我们经常使用到数据的复制,在.NET中有多种方式可以实现复制数据或对象。选择哪种方式通、是浅拷贝还是深拷贝,取决于对象的复杂性、数据量以及具体需求场景。

 1. MemberwiseClone拷贝

浅拷贝 Object.MemberwiseClone 方法 (System) | Microsoft Learn,指针对对象执行非静态字段的浅复制操作

  • 字段是基础类型如string、int,会全部复制过来,是全新的值
  • 字段是引用类型,则会则复制对象的引用,而不复制对象,二者对象是一个内存地址

深拷贝,则不管是字段还是引用类型,均完全实现全新的复现。

一般深拷贝可以手动实现,对象类内部添加Clone方法(也可以实现内置的统一接口ICloneable),将所有字段重新赋值一遍、返回一个新对象。那也可以基于MemberwiseClone方案之上,对引用类型重新赋值一个新对象,实现深拷贝

深拷贝,内部克隆的对象字段可以修改,不会影响原来对象的值。

参考如下代码:

 1     public class MemberwiseCloneModel
 2     {
 3         public int Age { get; set; }
 4         public string Name { get; set; }
 5         public TestMode Mode { get; set; }
 6         public MemberwiseCloneModel ShallowClone()
 7         {
 8             return (MemberwiseCloneModel)this.MemberwiseClone();
 9         }
10         public MemberwiseCloneModel DeepCopy()
11         {
12             var clone = (MemberwiseCloneModel)this.MemberwiseClone();
13             clone.Mode = new TestMode() { Data = this.Mode?.Data ?? string.Empty };
14             return clone;
15         }
16     }

2.Record的with数据拷贝

这是针对Record数据类的一类拷贝方式,只在C#9以上支持,详见Record - C# reference | Microsoft Learn

record因为是标记数据类,可以只有属性,所以RecordModel可以简写为RecordModel1结构:

1     public record class RecordModel
2     {
3         public string Name { get; set; }
4         public int Age { get; set; }
5         public TestMode Mode { get; set; }
6     }
7     public record RecordModel1(string Name, int Age, TestMode Mode);

with相当于MemberwiseClone浅拷贝,对值类型字段可以全新复制,但引用类型操作后还是同一对象 with 表达式 - 创建新对象,这些对象是现有对象的修改副本 - C# reference | Microsoft Learn

写个demo:

 1     public static void TestRecordWith()
 2     {
 3         var original = new RecordModel() { Name = "Test", Age = 20, Mode = new TestMode() { Data = "data" } };
 4         var clone = original with { };
 5         Debug.WriteLine($"referenceEquals:{ReferenceEquals(original, clone)}");
 6         Debug.WriteLine($"clone:{clone.Name},{clone.Age},{clone.Mode.Data}");
 7         clone.Name = "Test1";
 8         clone.Age = 21;
 9         clone.Mode.Data = "data1";
10         Debug.WriteLine($"original after modified clone:{original.Name},{original.Age},{original.Mode.Data}");
11     }

上面demo输出结果,基础类型不会被修改:

另外,with也可以同时给属性赋新值,var clone = original with { Name = "Test0" };

3. 序列化实现数据拷贝

可以通过将对象序列化为二进制、XML 或 JSON 等格式,然后再反序列化为新对象来实现深拷贝。此方法对内部引用对象字段,也适用
1)二进制格式实现比例简单,直接粘贴代码,如下:
 1     public static T DeepCopy<T>(T obj)
 2     {
 3         using (MemoryStream memoryStream = new MemoryStream())
 4         {
 5             IFormatter formatter = new BinaryFormatter();
 6             formatter.Serialize(memoryStream, obj);
 7             memoryStream.Seek(0, SeekOrigin.Begin);
 8             return (T)formatter.Deserialize(memoryStream);
 9         }
10     }

但BinaryFormatter在.NET5之后标记废弃了,原因是安全漏洞:使用 BinaryFormatter 和相关类型时的反序列化风险 - .NET | Microsoft Learn。官方推荐使用XML以及Json序列化等

2)XML序列化需要添加属性标记DataContract、DataMember(推荐Json序列化也添加此标记)

 1     [DataContract]
 2     public class SerializerModel
 3     {
 4         [DataMember]
 5         public string Name { get; set; }
 6         [DataMember]
 7         public int Age { get; set; }
 8         [DataMember]
 9         public TestMode Mode { get; set; }
10     }

DataContractSerializerDataContractSerializer 类 (System.Runtime.Serialization) | Microsoft Learn实现XML序列化:

1     public static T DeepCopyBySerializer<T>(T obj)
2     {
3         using var stream = new MemoryStream();
4         var serializer = new DataContractSerializer(typeof(T));
5         serializer.WriteObject(stream, obj);
6         stream.Position = 0;
7         return (T)serializer.ReadObject(stream);
8     }

XML序列化还有一个XmlSerializer,就不介绍了。

DataContractSerializer使用的是一种流式序列化方式,复杂对象、数据量较大时,DataContractSerializer比 XmlSerializer基于反射的序列化更快。如果是需要可视化可读性强的XML、数据量小、性能要求不高,可以使用XmlSerializer

3)再说说Json序列化

有两个有名的Json序列化器:微软的System.Text.Json和第三方成熟Newtonsoft.Json
如果是.NET版本推荐System.Text.Json,Framework版本使用Newtonsoft.Json。之前有统计过俩个方案的性能 .NET Json序列化方案选择 - 唐宋元明清2188 - 博客园
1     public static T DeepCopyByJson<T>(T obj)
2     {
3         var data = System.Text.Json.JsonSerializer.Serialize(obj);
4         return System.Text.Json.JsonSerializer.Deserialize<T>(data);
5     }

性能测试Benchmark

准备同样一个大小数据,Benchmark代码如下:

 1     [MemoryDiagnoser]
 2     public class BenchmarkTest
 3     {
 4         private readonly BenchmarkTestMode _data;
 5 
 6         public BenchmarkTest()
 7         {
 8             _data = GetData();
 9         }
10         [Benchmark]
11         public void ShallowCloneByMemberwiseClone()
12         {
13             var original = _data;
14             for (int i = 0; i < 1000; i++)
15             {
16                 var clone = original.ShallowClone();
17             }
18         }
19         [Benchmark]
20         public void ShallowCloneByRecordWith()
21         {
22             var original = _data;
23             for (int i = 0; i < 1000; i++)
24             {
25                 var clone = original with { };
26             }
27         }
28         [Benchmark]
29         public void DeepCloneByManual()
30         {
31             var original = _data;
32             for (int i = 0; i < 1000; i++)
33             {
34                 var benchmarkTestMode = new BenchmarkTestMode()
35                 {
36                     Angle = original.Angle,
37                     Name = original.Name,
38                     Points = original.Points.Select(i => new Point(i.X, i.Y)).ToList()
39                 };
40             }
41         }
42         [Benchmark]
43         public void DeepCloneByMemberwiseCloneManual()
44         {
45             var original = _data;
46             for (int i = 0; i < 1000; i++)
47             {
48                 var clone = original.DeepClone();
49             }
50         }
51         [Benchmark]
52         public void DeepCloneByDataContractSerializer()
53         {
54             var original = _data;
55             for (int i = 0; i < 1000; i++)
56             {
57                 using var stream = new MemoryStream();
58                 var serializer = new DataContractSerializer(typeof(BenchmarkTestMode));
59                 serializer.WriteObject(stream, original);
60                 stream.Position = 0;
61                 var clone = (BenchmarkTestMode)serializer.ReadObject(stream);
62             }
63         }
64         [Benchmark]
65         public void DeepCloneBySystemTextJson()
66         {
67             var original = _data;
68             for (int i = 0; i < 1000; i++)
69             {
70                 var data = System.Text.Json.JsonSerializer.Serialize(original);
71                 var clone = System.Text.Json.JsonSerializer.Deserialize<BenchmarkTestMode>(data);
72             }
73         }
74 
75         private BenchmarkTestMode GetData()
76         {
77             var original = new BenchmarkTestMode() { Name = "Test", Angle = 20 };
78             original.Points = new List<Point>();
79             for (int i = 0; i < 1000; i++)
80             {
81                 original.Points.Add(new Point(i, 1000 - i));
82             }
83             return original;
84         }
85     }
View Code

然后我们使用release把test跑起来

1     var summary = BenchmarkRunner.Run<BenchmarkTest>();
2     Console.WriteLine(summary);

1. 浅拷贝,我们对比MemberwiseClone 、Record数据类With

看下面测试结果,Record-with性能强的不是一丁点:

浅拷贝推荐Record数据类With操作,所以我们可以把record使用起来,record不只是简化以及可读性好。如果追求极致性能的话,可以使用record struct结构体

2. 深拷贝,主要有MemberwiseClone结合手动复制、手动复制、XML序列化、JSON序列化

XML/JSON序列化 性能远远小于 MemberwiseClone结合手动复制、手动复制。另外,序列化操作我们可以看到内存总量增加超级多,运行期间会带来一定的内存暴涨问题

所以大量数据场景,深拷贝推荐手动复制(可以结合MemberwiseClone),可以在组件库自定义一套解析、反解析接口,在团队内统一使用。如果只是快速实现功能、性能要求不高,可以使用XML/JSON序列化

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/869323.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

1.14 eclipse配置spring

今天完成了eclipse配置springboot eclipse本身并没有spring项目,需要在eclipse市场下载插件选择tool4安装安装完成等待eclipse加载,全部安装完成后即可创建spring项目

英语语法(标点符号:逗号和撇号)

结束句子的三种方法 认识逗号

深入浅出:Agent如何调用工具——从OpenAI Function Call到CrewAI框架

深入浅出:Agent如何调用工具——从OpenAI Function Call到CrewAI框架 嗨,大家好!作为一个喜欢折腾AI新技术的算法攻城狮,最近又学习了一些Agent工作流调用工具的文章,学完之后,我真的是“啊这”,一边感慨AI技术的强大,一边觉得自己打开了新世界的大门。于是,我决定写这…

在Ubantu中安装pycharm

1.下载pycharm linux版,我下载的是2022.3.3专业版 2. 更改host文件,输入: sudo gedit /etc/hosts在弹出的文件中的末尾加以下代码: 0.0.0.0 account.jetbrains.com3.激活pycharm: 将pycharm补丁jet-netfilter拷入ubantu中某一路径(注意是整个文件夹放进去,不要只放jar包…

docker-compose自动部署go项目全流程,本地到镜像仓库到服务器,踩坑笔记

声明:个人所学记录,有可以改进的地方希望不吝指教 Dockerfile # 使用golang官方镜像作为构建环境 FROM golang:1.23-alpine AS builder# 设置工作目录 WORKDIR /app# 设置环境变量镜像变量 ENV GO111MODULE=on ENV GOPROXY=https://goproxy.cn,direct# 复制go.mod 和 go.sum文…

docker部署d2l环境

编写dockerfile # 使用NVIDIA提供的CUDA基础镜像,包含CUDA 11.8.0和cuDNN 8,基于Ubuntu 22.04 FROM nvidia/cuda:11.8.0-cudnn8-devel-ubuntu22.04 # 设置维护者信息 MAINTAINER watcherprime <woma@126.com># 设置环境变量,包括时区、非交互式前端和PATH变量 ENV TZ=…

【TCP协议】TCP Keepalive 指南

1、什么是 TCP Keepalive?TCP Keepalive 是一种 TCP 协议内置的探测机制,用于检测长时间未活动的连接是否仍然存活。当启用了 Keepalive 后,TCP 会在连接空闲一定时间后,定期向对端发送探测包,如果未收到对端的响应,则会尝试多次探测,最终关闭连接。 用途: 检测并清理死…

《CPython Internals》阅读笔记:p151-p151

《CPython Internals》学习第 9天,p151-p1510 总结,总计 1 页。 一、技术总结 无。 二、英语总结(生词:1) 1.marshal (1)marshaling Marshalling or marshaling(US spelling) is the process of transforming the memory representation of an object into a data form su…

# vm逆向

vm逆向 虚拟机逆向与实现-CSDN博客 对上面博客的总结。 引 vm逆向题,一般是小型虚拟机程序,可以理解为一种模拟器,有start,dispatcher,opcode等结构。常见使用while-switch/if这类循环+选择结构来实现简单的虚拟机模拟,如下:逆向重点:分析入口,搞清输入和opcode的位置理…

【Gossip 协议】Redis 集群中节点之间的通信方式?

# 分布式系统 # Gossip 协议 在分布式系统中,不同的节点进行数据/信息共享是一个基本的需求。 一种比较简单粗暴的方法就是 集中式发散消息,简单来说就是一个主节点同时共享最新信息给其他所有节点,比较适合中心化系统。这种方法的缺陷也很明显,节点多的时候不光同步消息的…

推荐一款超棒的 Minecraft 启动器:Voxelum/x-minecraft-launcher

X Minecraft Launcher (XMCL) 是一个便于你管理多种整合包、模组、资源包、光影包的现代化启动器。它还支持 Minecraft Forge、 Fabric、Quilt、CurseForge 和 Modrinth它具有以下令人心动的特点:多版本兼容性:支持多个 Minecraft 版本,正式版和愚人节版本。自动化资源下载与…

互联网大中小厂实习面经:滴滴、美团、货拉拉、蔚来、信通院等

本文介绍Momenta、蔚来、中国信息通信研究院、昆仑万维、滴滴、易智瑞等企业各类技术岗位的暑期实习、日常实习面试流程与具体问题~本文介绍Momenta、蔚来、中国信息通信研究院、昆仑万维、滴滴、易智瑞等企业各类技术岗位的暑期实习、日常实习面试流程与具体问题。在前一段时间…