.NET 数据拷贝方案选择

打印 上一主题 下一主题

主题 1013|帖子 1013|积分 3039

 应用中我们常常利用到数据的复制,在.NET中有多种方式可以实现复制数据或对象。选择哪种方式、是浅拷贝还是深拷贝,具体需求场景可以取决于对象的复杂性、数据量等,本文我们介绍主要的拷贝方式以及相对高性能的方案。
 1. MemberwiseClone拷贝

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

  • 字段是底子类型如string、int,会全部复制过来,是全新的值
  • 字段是引用类型,则会则复制对象的引用,而不复制对象,二者对象是一个内存地址
深拷贝,则不管是字段还是引用类型,均完全实现全新的复现。
一般深拷贝可以手动实现,对象类内部添加Clone方法(也可以实现内置的同一接口ICloneable),将全部字段重新赋值一遍、返回一个新对象。那也可以基于MemberwiseClone方案之上,对引用类型重新赋值一个新对象,实现深拷贝
深拷贝,内部克隆的对象字段可以修改,不会影响原来对象的值。
参考如下代码:
  1. 1     public class MemberwiseCloneModel
  2. 2     {
  3. 3         public int Age { get; set; }
  4. 4         public string Name { get; set; }
  5. 5         public TestMode Mode { get; set; }
  6. 6         public MemberwiseCloneModel ShallowClone()
  7. 7         {
  8. 8             return (MemberwiseCloneModel)this.MemberwiseClone();
  9. 9         }
  10. 10         public MemberwiseCloneModel <strong>DeepCopy</strong>()
  11. 11         {
  12. 12             var clone = (MemberwiseCloneModel)this.MemberwiseClone();
  13. 13             clone.Mode = new TestMode() { Data = this.Mode?.Data ?? string.Empty };
  14. 14             return clone;
  15. 15         }
  16. 16     }
复制代码
2.Record的with数据拷贝

这是针对Record数据类的一类拷贝方式,只在C#9以上支持,详见Record - C# reference | Microsoft Learn
record由于是标记数据类,可以只有属性,所以RecordModel可以简写为RecordModel1结构:
  1. 1     public record class RecordModel
  2. 2     {
  3. 3         public string Name { get; set; }
  4. 4         public int Age { get; set; }
  5. 5         public TestMode Mode { get; set; }
  6. 6     }
  7. 7     public record RecordModel1(string Name, int Age, TestMode Mode);
复制代码
with相当于MemberwiseClone浅拷贝,对值类型字段可以全新复制,但引用类型操纵后还是同一对象 with 表达式 - 创建新对象,这些对象是现有对象的修改副本 - C# reference | Microsoft Learn
写个demo:
  1. 1     public static void TestRecordWith()
  2. 2     {
  3. 3         var original = new RecordModel() { Name = "Test", Age = 20, Mode = new TestMode() { Data = "data" } };
  4. 4         var clone = original with { };
  5. 5         Debug.WriteLine($"referenceEquals:{ReferenceEquals(original, clone)}");
  6. 6         Debug.WriteLine($"clone:{clone.Name},{clone.Age},{clone.Mode.Data}");
  7. 7         clone.Name = "Test1";
  8. 8         clone.Age = 21;
  9. 9         clone.Mode.Data = "data1";
  10. 10         Debug.WriteLine($"original after modified clone:{original.Name},{original.Age},{original.Mode.Data}");
  11. 11     }
复制代码
上面demo输出效果,底子类型不会被修改:

别的,with也可以同时给属性赋新值,var clone = original with { Name = "Test0" };
3. 序列化实现数据拷贝

可以通过将对象序列化为二进制、XML 或 JSON 等格式,然后再反序列化为新对象来实现深拷贝。此方法对内部引用对象字段,也适用1)二进制格式实现比例简单,直接粘贴代码,如下:
  1. 1     public static T DeepCopy<T>(T obj)
  2. 2     {
  3. 3         using (MemoryStream memoryStream = new MemoryStream())
  4. 4         {
  5. 5             IFormatter formatter = new BinaryFormatter();
  6. 6             formatter.Serialize(memoryStream, obj);
  7. 7             memoryStream.Seek(0, SeekOrigin.Begin);
  8. 8             return (T)formatter.Deserialize(memoryStream);
  9. 9         }
  10. 10     }
复制代码
但BinaryFormatter在.NET5之后标记废弃了,原因是安全漏洞:利用 BinaryFormatter 和相关类型时的反序列化风险 - .NET | Microsoft Learn。官方保举利用XML以及Json序列化等
2)XML序列化必要添加属性标记DataContract、DataMember(保举Json序列化也添加此标记)
  1. 1     [DataContract]
  2. 2     public class SerializerModel
  3. 3     {
  4. 4         [DataMember]
  5. 5         public string Name { get; set; }
  6. 6         [DataMember]
  7. 7         public int Age { get; set; }
  8. 8         [DataMember]
  9. 9         public TestMode Mode { get; set; }
  10. 10     }
复制代码
DataContractSerializerDataContractSerializer 类 (System.Runtime.Serialization) | Microsoft Learn实现XML序列化:
  1. 1     public static T DeepCopyBySerializer<T>(T obj)
  2. 2     {
  3. 3         using var stream = new MemoryStream();
  4. 4         var serializer = new DataContractSerializer(typeof(T));
  5. 5         serializer.WriteObject(stream, obj);
  6. 6         stream.Position = 0;
  7. 7         return (T)serializer.ReadObject(stream);
  8. 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. 1     public static T DeepCopyByJson<T>(T obj)
  2. 2     {
  3. 3         var data = System.Text.Json.JsonSerializer.Serialize(obj);
  4. 4         return System.Text.Json.JsonSerializer.Deserialize<T>(data);
  5. 5     }
复制代码
性能测试Benchmark

预备同样一个大小数据,Benchmark代码如下:
  1. 1     [MemoryDiagnoser]
  2. 2     public class BenchmarkTest
  3. 3     {
  4. 4         private readonly BenchmarkTestMode _data;
  5. 5
  6. 6         public BenchmarkTest()
  7. 7         {
  8. 8             _data = GetData();
  9. 9         }
  10. 10         [Benchmark]
  11. 11         public void ShallowCloneByMemberwiseClone()
  12. 12         {
  13. 13             var original = _data;
  14. 14             for (int i = 0; i < 1000; i++)
  15. 15             {
  16. 16                 var clone = original.ShallowClone();
  17. 17             }
  18. 18         }
  19. 19         [Benchmark]
  20. 20         public void ShallowCloneByRecordWith()
  21. 21         {
  22. 22             var original = _data;
  23. 23             for (int i = 0; i < 1000; i++)
  24. 24             {
  25. 25                 var clone = original with { };
  26. 26             }
  27. 27         }
  28. 28         [Benchmark]
  29. 29         public void DeepCloneByManual()
  30. 30         {
  31. 31             var original = _data;
  32. 32             for (int i = 0; i < 1000; i++)
  33. 33             {
  34. 34                 var benchmarkTestMode = new BenchmarkTestMode()
  35. 35                 {
  36. 36                     Angle = original.Angle,
  37. 37                     Name = original.Name,
  38. 38                     Points = original.Points.Select(i => new Point(i.X, i.Y)).ToList()
  39. 39                 };
  40. 40             }
  41. 41         }
  42. 42         [Benchmark]
  43. 43         public void DeepCloneByMemberwiseCloneManual()
  44. 44         {
  45. 45             var original = _data;
  46. 46             for (int i = 0; i < 1000; i++)
  47. 47             {
  48. 48                 var clone = original.DeepClone();
  49. 49             }
  50. 50         }
  51. 51         [Benchmark]
  52. 52         public void DeepCloneByDataContractSerializer()
  53. 53         {
  54. 54             var original = _data;
  55. 55             for (int i = 0; i < 1000; i++)
  56. 56             {
  57. 57                 using var stream = new MemoryStream();
  58. 58                 var serializer = new DataContractSerializer(typeof(BenchmarkTestMode));
  59. 59                 serializer.WriteObject(stream, original);
  60. 60                 stream.Position = 0;
  61. 61                 var clone = (BenchmarkTestMode)serializer.ReadObject(stream);
  62. 62             }
  63. 63         }
  64. 64         [Benchmark]
  65. 65         public void DeepCloneBySystemTextJson()
  66. 66         {
  67. 67             var original = _data;
  68. 68             for (int i = 0; i < 1000; i++)
  69. 69             {
  70. 70                 var data = System.Text.Json.JsonSerializer.Serialize(original);
  71. 71                 var clone = System.Text.Json.JsonSerializer.Deserialize<BenchmarkTestMode>(data);
  72. 72             }
  73. 73         }
  74. 74
  75. 75         private BenchmarkTestMode GetData()
  76. 76         {
  77. 77             var original = new BenchmarkTestMode() { Name = "Test", Angle = 20 };
  78. 78             original.Points = new List<Point>();
  79. 79             for (int i = 0; i < 1000; i++)
  80. 80             {
  81. 81                 original.Points.Add(new Point(i, 1000 - i));
  82. 82             }
  83. 83             return original;
  84. 84         }
  85. 85     }
复制代码
View Code然后我们利用release把test跑起来
  1. 1     var summary = BenchmarkRunner.Run<BenchmarkTest>();
  2. 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.cnblogs.com/kybs0/让学习成为风俗,假设明天就有重大机会等着你,你预备好了么本文版权归作者和博客园共有,接待转载,但未经作者同意必须在文章页面给出原文连接,否则保留追究法律责任的权利。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

商道如狼道

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表