English 简体中文 繁體中文 한국 사람 日本語 Deutsch русский بالعربية TÜRKÇE português คนไทย french
查看: 7|回复: 0

.NET 数据拷贝方案选择

[复制链接]
查看: 7|回复: 0

.NET 数据拷贝方案选择

[复制链接]
查看: 7|回复: 0

316

主题

0

回帖

958

积分

高级会员

积分
958
自动销售设备

316

主题

0

回帖

958

积分

高级会员

积分
958
2025-2-6 17:55:17 | 显示全部楼层 |阅读模式
 应用中我们经常使用到数据的复制,在.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 RecordModel2     {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序列化

已知最强的2个Json序列化器:微软的System.Text.Json和第三方成熟Newtonsoft.Json
如果是.NET版本推荐System.Text.Json,Framework版本使用Newtonsoft.Json。之前有统计过俩个方案的性能 .NET Json序列化方案选择 - 唐宋元明清2188 - 博客园
后面关注.NET8+,所以看System.Text.Json就好:
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     }
4. 第三方库 AutoMapper、DeepCloner等

补充下第三方库的使用
1     public static T DeepCopyByAutoMapper<T>(T obj) 2     { 3         var config = new MapperConfiguration(cfg => 4         { 5             cfg.CreateMap<T, T>(); 6         }); 7         var mapper = config.CreateMapper(); 8         T clone = mapper.Map<T, T>(obj); 9         return clone;10     }11     public static T DeepCopyByDeepCloner<T>(T obj)12     {13         return obj.DeepClone();14     }
性能测试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.InnerShallowClone(); 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.InnerDeepClone(); 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         [Benchmark] 75         public void DeepCopyByAutoMapper() 76         { 77             var original = _data; 78             for (int i = 0; i < 1000; i++) 79             { 80                 var clone = DeepCopyByAutoMapper(original); 81             } 82         } 83         [Benchmark] 84         public void DeepCopyByDeepCloner() 85         { 86             var original = _data; 87             for (int i = 0; i < 1000; i++) 88             { 89                 var clone = original.DeepClone(); 90             } 91         } 92         private T DeepCopyByAutoMapper<T>(T original) 93         { 94             var config = new MapperConfiguration(cfg => 95             { 96                 cfg.CreateMap<T, T>(); 97             }); 98             var mapper = config.CreateMapper(); 99             T clone = mapper.Map<T, T>(original);100             return clone;101         }102         private BenchmarkTestMode GetData()103         {104             var original = new BenchmarkTestMode() { Name = "Test", Angle = 20 };105             original.Points = new List<Point>();106             for (int i = 0; i < 1000; i++)107             {108                 original.Points.Add(new Point(i, 1000 - i));109             }110             return original;111         }112     }113     [DataContract]114     public record class BenchmarkTestMode115     {116         [DataMember]117         public string Name { get; set; }118         [DataMember]119         public int Angle { get; set; }120         [DataMember]121         public List<Point> Points { get; set; }122         public BenchmarkTestMode InnerShallowClone()123         {124             return (BenchmarkTestMode)this.MemberwiseClone();125         }126         public BenchmarkTestMode InnerDeepClone()127         {128             var clone = (BenchmarkTestMode)this.MemberwiseClone();129             clone.Points = Points.Select(i => new Point(i.X, i.Y)).ToList();130             return clone;131         }132     }
然后我们使用release把test跑起来
1     var summary = BenchmarkRunner.Run<BenchmarkTest>();2     Console.WriteLine(summary);
1. 浅拷贝,我们对比MemberwiseClone 、Record数据类With
看下面测试结果,Record-with性能强的不是一丁点:

浅拷贝推荐Record数据类With操作,所以我们可以把record使用起来,record不只是简化以及可读性好。如果追求极致性能的话可以使用record struct结构体,record struct也是支持with操作。
2. 深拷贝,主要有MemberwiseClone结合手动复制、手动复制、XML序列化、JSON序列化,第三方库AutoMapper、DeepCloner
执行行Benchmark测试,结果如下:

XML/JSON序列化 性能远远小于 手动复制、DeepCloner。另外,序列化操作我们可以看到内存总量增加超级多,运行期间会带来一定的内存暴涨问题。
所以大量复杂类似数据场景,深拷贝推荐使用第三方库DeepCloner,有精力的团队可以把DeepClone放在本地管理、研究。比如上面的大量数组数据,这方面相对手动复制优化了较多性能。以下是DeepCloner force-net/DeepCloner:适用于 .NET 的快速对象克隆器优化的一些点,我翻译了下:

一般情况下推荐手动复制(或者结合MemberwiseClone),简单类型的属性对象,使用手动复制性能是最高的,可以在组件库自定义一套解析、反解析接口,在团队内统一使用。
如果只是快速实现功能、性能要求不高,可以使用XML/JSON序列化
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

316

主题

0

回帖

958

积分

高级会员

积分
958

QQ|智能设备 | 粤ICP备2024353841号-1

GMT+8, 2025-3-10 15:46 , Processed in 2.004787 second(s), 30 queries .

Powered by 智能设备

©2025

|网站地图