找回密码
 立即注册
首页 业界区 业界 使用c#强大的SourceGenerator现对象的深克隆 ...

使用c#强大的SourceGenerator现对象的深克隆

雨角 5 小时前
去年的时候写了一篇用使用c#强大的表达式树实现对象的深克隆. 最近又看到园子里的另外一篇吐槽automapper性能的文章。正好闲来无事,就想着看如果用Source Generator来实现深克隆,性能上会不会比表达式树更强劲呢,于是有了这篇文章。
之前使用表达式树深克隆的的代码可以实现类型相同/不同之间的克隆(UserEntity->UserDto/UserEntity->UserEntity),支持环状引用(即A的属性引用自身或者A的属性是类型B,类型B中有属性引用A)和可空->不可空转换(public int? id->public int id)。支持枚举转换(public enumXXX type->public int type/public int type->public enumXXX type),在实际生产环境中一直稳定使用,一直没有遇到过问题,但是对于性能上到底能够比手写深拷贝快多少,一直没有闲心去测,这一次正好干脆弄一个Source Generator的版本,再以手写深克隆为基准来实现。
测试环境为windows10、.net版本是9.0.8、引用的BenchmarkDotNet版本是0.15.2。
测试的Dto结构如下:
  1. public class DtoTest
  2. {
  3.     public int? Id { get; set; }
  4.     public string Name { get; set; }
  5.     public List<ChildDto<DtoTest>> Items { get; set; }
  6.     public List<string> Tags { get; set; }
  7.     public Dictionary<string, int?> Dict { get; set; }
  8.     public TestEnum TestEnum { get; set; }
  9.     public int? TestEnum2 { get; set; }
  10.     public DtoTest This { get; set; }
  11. }
  12. public class DtoTest2
  13. {
  14.     public int Id { get; set; }
  15.     public string Name { get; set; }
  16.     public List<ChildDto<DtoTest2>?>? Items { get; set; }
  17.     public List<string> Tags { get; set; }
  18.     public Dictionary<string, int> Dict { get; set; }
  19.     public int? TestEnum { get; set; }
  20.     public TestEnum TestEnum2 { get; set; }
  21.     public DtoTest2 This { get; set; }
  22. }
  23. public class ChildDto<T>
  24. {
  25.     public string Key { get; set; }   
  26.     public int Value { get; set; }
  27.     public T Mother { get; set; }
  28. }
  29. public enum TestEnum
  30. {
  31.     Take = 0,
  32.     Sale = 1,
  33.     Pull = 2
  34. }
复制代码
可以看到这份代码基本还是覆盖了大部分常见的情况,包含泛型、字典、可空转换、枚举等等。
测试时我的类型实例构造如下:
  1. _src = new DtoTest
  2. {
  3.     Id = 123,
  4.     Name = "hello world",
  5.     Tags = new List<string> { "a", "b", "c" },
  6.     Items = new List<ChildDto<DtoTest>>
  7.             {
  8.                 new ChildDto<DtoTest> { Key = "k1", Value = 42 },
  9.                 new ChildDto<DtoTest> { Key = "k2", Value = 100 }
  10.             },
  11.     Dict = new Dictionary<string, int?> { ["x"] = 1, ["y"] = 2 },
  12.     TestEnum = TestEnum.Sale,
  13.     TestEnum2 = null
  14. };
  15. _src.This = _src;
  16. _src.Items.Add(_src.Items[0]);
  17. foreach (var item in _src.Items)
  18. {
  19.     item.Mother = _src;
  20. }
复制代码
增加了循环引用和多次拷贝(items有三个子对象但是有两个指向同一个引用),基本上能够覆盖大部分深拷贝场景了。接下来就是运行后的截图
接下来是增加了一部分测试,确保深拷贝确实是递归到所有属性及其子属性的,而不是简单的浅拷贝(即修改原始对象属性会导致克隆的新对象的属性变化(比如属性的集合内容和属性自身的子属性随着变化)):
测试代码如下:
  1. var _src = new DtoTest
  2. {
  3.     Id = 123,
  4.     Name = "hello world",
  5.     Tags = new List<string> { "a", "b", "c" },
  6.     Items = new List<ChildDto<DtoTest>>
  7.             {
  8.                 new ChildDto<DtoTest> { Key = "k1", Value = 42 },
  9.                 new ChildDto<DtoTest> { Key = "k2", Value = 100 }
  10.             },
  11.     Dict = new Dictionary<string, int?> { ["x"] = 1, ["y"] = 2 },
  12.     TestEnum = TestEnum.Sale,
  13.     TestEnum2 = null
  14. };
  15. _src.This = _src;
  16. _src.Items.Add(_src.Items[0]);
  17. foreach (var item in _src.Items)
  18. {
  19.     item.Mother = _src;
  20. }var aaa = DeepClone.DeepCloneHelper.CopyTo(_src);var bbb = InfrastructureBase.Object.ExtensionMapper.Map(_src);var jsonOpts = new JsonSerializerOptions { ReferenceHandler = ReferenceHandler.Preserve };_src.Id = 222;_src.Name = "bbb";_src.Tags[0] = "d";_src.Items.First().Key = "k0";_src.Dict["x"] = -1;Console.WriteLine(JsonSerializer.Serialize(_src, jsonOpts));Console.WriteLine(JsonSerializer.Serialize(aaa, jsonOpts));Console.WriteLine(JsonSerializer.Serialize(bbb, jsonOpts));
复制代码
打印的json结果:
1.png

 可以看到对src的属性修改并没有影响到SG和EXP生成的新对象。
最后是benchmark的情况如下:
2.png

 从结果来看,Ratio这一行中以手写深拷贝(new dto2(){xxx= dto1.xxx...})作为基准值1的情况下,Source Generator大概是其2倍成本,而表达式树的版本大概在其5倍+左右的成本。cong Gen0 垃圾回收代以及Allocated每次执行方法分配的内存大小来看三者的成本差异不大,都在同一个区间。Alloc Ratio内存分配比来看SG和EXP的分配版本也落在X1~X2之间,属于基本可以接受的范畴。
具体的技术细节就不聊了,大家有兴趣可以到github上下载对应的代码测试,上面包含了完整的两种深克隆的实现:https://github.com/sd797994/SG_Extension_DeepClone

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册