使用c#强大的SourceGenerator现对象的深克隆
去年的时候写了一篇用使用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结构如下:
public class DtoTest
{
public int? Id { get; set; }
public string Name { get; set; }
public List<ChildDto<DtoTest>> Items { get; set; }
public List<string> Tags { get; set; }
public Dictionary<string, int?> Dict { get; set; }
public TestEnum TestEnum { get; set; }
public int? TestEnum2 { get; set; }
public DtoTest This { get; set; }
}
public class DtoTest2
{
public int Id { get; set; }
public string Name { get; set; }
public List<ChildDto<DtoTest2>?>? Items { get; set; }
public List<string> Tags { get; set; }
public Dictionary<string, int> Dict { get; set; }
public int? TestEnum { get; set; }
public TestEnum TestEnum2 { get; set; }
public DtoTest2 This { get; set; }
}
public class ChildDto<T>
{
public string Key { get; set; }
public int Value { get; set; }
public T Mother { get; set; }
}
public enum TestEnum
{
Take = 0,
Sale = 1,
Pull = 2
}
可以看到这份代码基本还是覆盖了大部分常见的情况,包含泛型、字典、可空转换、枚举等等。
测试时我的类型实例构造如下:
_src = new DtoTest
{
Id = 123,
Name = "hello world",
Tags = new List<string> { "a", "b", "c" },
Items = new List<ChildDto<DtoTest>>
{
new ChildDto<DtoTest> { Key = "k1", Value = 42 },
new ChildDto<DtoTest> { Key = "k2", Value = 100 }
},
Dict = new Dictionary<string, int?> { ["x"] = 1, ["y"] = 2 },
TestEnum = TestEnum.Sale,
TestEnum2 = null
};
_src.This = _src;
_src.Items.Add(_src.Items[0]);
foreach (var item in _src.Items)
{
item.Mother = _src;
}
增加了循环引用和多次拷贝(items有三个子对象但是有两个指向同一个引用),基本上能够覆盖大部分深拷贝场景了。接下来就是运行后的截图
接下来是增加了一部分测试,确保深拷贝确实是递归到所有属性及其子属性的,而不是简单的浅拷贝(即修改原始对象属性会导致克隆的新对象的属性变化(比如属性的集合内容和属性自身的子属性随着变化)):
测试代码如下:
var _src = new DtoTest
{
Id = 123,
Name = "hello world",
Tags = new List<string> { "a", "b", "c" },
Items = new List<ChildDto<DtoTest>>
{
new ChildDto<DtoTest> { Key = "k1", Value = 42 },
new ChildDto<DtoTest> { Key = "k2", Value = 100 }
},
Dict = new Dictionary<string, int?> { ["x"] = 1, ["y"] = 2 },
TestEnum = TestEnum.Sale,
TestEnum2 = null
};
_src.This = _src;
_src.Items.Add(_src.Items[0]);
foreach (var item in _src.Items)
{
item.Mother = _src;
}
var aaa = DeepClone.DeepCloneHelper.CopyTo<DtoTest, DtoTest2>(_src);
var bbb = InfrastructureBase.Object.ExtensionMapper<DtoTest, DtoTest2>.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结果:

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

从结果来看,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
使用c#强大的SourceGenerator现对象的深克隆的更多相关文章
- JS如何进行对象的深克隆(深拷贝)?
JS中,一般的赋值传递的都是对象/数组的引用,并没有真正的深拷贝一个对象,如何进行对象的深拷贝呢? var a = {name : 'miay'}; var b = a; b.name = 'Jone ...
- iOS-实现对象拷贝【对象拷贝】
对象引用 NSCopying 代理 .h @interface xk : NSObject <NSCopying> @property (nonatomic, copy) NSString ...
- js 对象的深克隆
前端笔试或者面试的时候,很喜欢问的一个问题就是对象的深度克隆,或者说是对象的深度复制.其实这个问题说容易很容易,但是要说全面也挺不易. 要弄明白对象的克隆,首先要明白js中对象的组成.在js中一切实例 ...
- 【转】ADO.NET中的五个主要对象
Connection 物件 Connection 对象主要是开启程序和数据库之间的连结.没有利用连结对象将数据库打开,是无法从数据库中取得数据的.这个物件在ADO.NET 的最底层,我们可以自己 ...
- common-pool2对象池(连接池)的介绍及使用
我们在服务器开发的过程中,往往会有一些对象,它的创建和初始化需要的时间比较长,比如数据库连接,网络IO,大数据对象等.在大量使用这些对象时,如果不采用一些技术优化,就会造成一些不可忽略的性能影响.一种 ...
- JavaScript事件---事件对象
发文不易,若转载传播,请亲注明出处,谢谢! 内容提纲: 1.事件对象 2.鼠标事件 3.键盘事件 4.W3C与IE JavaScript事件的一个重要方面是它们拥有一些相对一致的特点,可以给你的开 ...
- [Effective JavaScript 笔记]第28条:不要信赖函数对象的toString方法
js函数有一个非凡的特性,即将其源代码重现为字符串的能力. (function(x){ return x+1 }).toString();//"function (x){ return x+ ...
- QTP自传之对象
对象在手,测试我有 大家别误会,这里说的对象可不是值指男女朋友,而是对被测控件的识别.经过昨天的录制,大家一定很奇怪为什么我可以做到精确的回放操作,这都要归功于对象,下面就隆重的介绍我在对象识别方面的 ...
- 第一百二十节,JavaScript事件对象
JavaScript事件对象 学习要点: 1.事件对象 2.鼠标事件 3.键盘事件 4.W3C与IE JavaScript事件的一个重要方面是它们拥有一些相对一致的特点,可以给你的开发提供更多的强大功 ...
- JSP内置对象--pageContext对象(非常重要!!!)
pageContext对象是javax.servlet.jsp.PageContext类的实例,只要表示的是一个jsp页面的上下文,而且功能强大,几乎可以操作各种内置对象. >forward(S ...
随机推荐
- FastAPI如何玩转安全防护,让黑客望而却步?
扫描二维码 关注或者微信搜一搜:编程智域 前端至全栈交流与成长 发现1000+提升效率与开发的AI工具和实用程序:https://tools.cmdragon.cn/ 1. FastAPI安全基础架构 ...
- 4.分布式事务方案-Saga
1. Saga是什么 保证最终一致性的一种分布式事务方案 2. Saga流程 有多个事务参与者,每个参与者都有两块逻辑:正向操作和逆向操作 把事务分成两个阶段 第一阶段每个参与者执行正向操作 第二阶段 ...
- Google play安装不上Chrome
背景 今天我的(安卓-小米)手机上突然要用到chrome,于是打开了Google Play商店下载. 发现一个问题,每次下载到百分百后,安装进度就会一闪而过,进而重新下载. 我尝试安装chrome b ...
- antd vue3 图片 手动上传
<template> <a-upload v-model:file-list="fileList" name="avatar" list-ty ...
- HDU7458 旅行 题解
前言 感觉是非常优秀的题目,写一篇题解记录一下. HDU-7458 旅行(on Vjudge) 题面 题目描述 有一棵 \(n\) 个结点的无根树,每个结点都有对应的类型 \(c_i\) 和权重 \( ...
- java 聊天 两个进程互相通信开两个线程
简介 RT code server package com.kuang; import java.io.BufferedReader; import java.io.IOException; impo ...
- Android List数组列表自定义排序
自定义排序 例如:根据文件的最后修改时间进行排序,最新文件在前 Collections.sort(lstFiles, new Comparator<FileListData>() { @O ...
- RestCloud ETL社区 八月精选问答
- SciTech-BigDataAIML-Machine Learning Tutorials
Machine Learning Tutorials Machine Learning Tutorials This page lists all of the machine learning tu ...
- linux 笔记 (1)
du查看某个文件或目录占用磁盘空间的大小 du -sh : 查看当前目录总共占的容量.而不单独列出各子项占用的容量 du --max-depth=1 -h:查看当前文件夹下磁盘的使用状况 特殊字符: ...