PocoEmit遥遥领先于AutoMapper之循环引用

news/2025/9/28 0:13:55/文章来源:https://www.cnblogs.com/xiangji/p/19115937

PocoEmit遥遥领先于AutoMapper之循环引用

一、什么是循环引用

循环引用就是类型相互依赖

1. 比如A类有B类的属性,B类也有A类的属性

  • 这有什么问题呢?
  • 编写生成A的代码需要遍历A的所有属性
  • 构造B类型属性是A代码的一部分,B代码又含有A类型属性
  • 这就是一个编译死循环

2. 其他循环引用的例子

  • 链表结构只有一个类型也是类型循环引用
  • A-B-C-A等更长的引用链条也会构成类型循环引用

二、举个树状结构的Case

  • 树状结构在实际应用中很常见

1. 导航菜单代码

导航菜单是一个典型的树状结构

public class Menu
{public int Id { get; set; }public string Name { get; set; }public string Description { get; set; }public List<Menu> Children { get; set; }public static Menu GetMenu(){var programs = new Menu { Id = 2, Name = "Programs", Description = "程序" };var documents = new Menu { Id = 3, Name = "Documents", Description = "文档" };var settings = new Menu { Id = 4, Name = "Settings", Description = "设置" };var help = new Menu { Id = 5, Name = "Help", Description = "帮助" };var run = new Menu { Id = 6, Name = "Run", Description = "运行" };var shutdown = new Menu { Id = 7, Name = "Shut Down", Description = "关闭" };var start = new Menu { Id = 1, Name = "Start", Description = "开始", Children = [programs, documents, settings, help, run, shutdown] };return start;}
}

2. 把Menu转化为MenuDTO

2.1 PocoEmit执行代码

  • 代码中多加了UseCollection
  • 如果全局开启了集合就不需要这行代码
var menu = Menu.GetMenu();
var mapper = PocoEmit.Mapper.Create().UseCollection();
var dto = mapper.Convert<Menu, MenuDTO>(menu);

2.2 执行效果如下:

  • 以下测试是使用vscode执行的(需要Jupyter Notebook插件)
  • 测试代码地址为: https://github.com/donetsoftwork/MyEmit/tree/main/Notes/menu.dib
  • gitee地址: https://gitee.com/donetsoftwork/MyEmit/tree/main/Notes/menu.dib
{"$id": "1","Id": 1,"Name": "Start","Description": "\u5F00\u59CB","Children": {"$id": "2","$values": [{"$id": "3","Id": 2,"Name": "Programs","Description": "\u7A0B\u5E8F","Children": null},{"$id": "4","Id": 3,"Name": "Documents","Description": "\u6587\u6863","Children": null},{"$id": "5","Id": 4,"Name": "Settings","Description": "\u8BBE\u7F6E","Children": null},{"$id": "6","Id": 5,"Name": "Help","Description": "\u5E2E\u52A9","Children": null},{"$id": "7","Id": 6,"Name": "Run","Description": "\u8FD0\u884C","Children": null},{"$id": "8","Id": 7,"Name": "Shut Down","Description": "\u5173\u95ED","Children": null}]}
}

3. 与AutoMapper性能对比如下

Method Mean Error StdDev Median Ratio RatioSD Gen0 Gen1 Allocated Alloc Ratio
Auto 320.14 ns 0.420 ns 0.484 ns 320.10 ns 5.51 0.10 0.0751 0.0003 1296 B 2.95
AutoFunc 289.80 ns 6.580 ns 7.313 ns 295.77 ns 4.98 0.15 0.0751 0.0003 1296 B 2.95
Poco 58.17 ns 1.031 ns 1.103 ns 58.17 ns 1.00 0.03 0.0255 - 440 B 1.00
PocoFunc 48.10 ns 1.059 ns 1.087 ns 49.06 ns 0.83 0.02 0.0255 - 440 B 1.00
  • AutoMapper耗时是Poco的5倍多
  • AutoMapper内存是Poco的近3倍
  • 哪怕是用上AutoMapper内部生成的委托也挽救不了多少局面

4. 我们增加无循环引用再测试一下

4.1 无循环引用菜单

public class Menu0
{public int ParentId { get; set; }public int Id { get; set; }public string Name { get; set; }public string Description { get; set; }public static List<Menu0> GetMenus(){var start = new Menu0 { Id = 1, Name = "Start", Description = "开始", ParentId = 0 };var programs = new Menu0 { Id = 2, Name = "Programs", Description = "程序", ParentId = 1 };var documents = new Menu0 { Id = 3, Name = "Documents", Description = "文档", ParentId = 1 };var settings = new Menu0 { Id = 4, Name = "Settings", Description = "设置", ParentId = 1 };var help = new Menu0 { Id = 5, Name = "Help", Description = "帮助", ParentId = 1 };var run = new Menu0 { Id = 6, Name = "Run", Description = "运行" , ParentId = 1 };var shutdown = new Menu0 { Id = 7, Name = "Shut Down", Description = "关闭", ParentId = 1 };return [start, programs, documents, settings, help, run, shutdown];}
}

4.2 性能测试如下

Method Mean Error StdDev Median Ratio RatioSD Gen0 Gen1 Allocated Alloc Ratio
Auto 320.14 ns 0.420 ns 0.484 ns 320.10 ns 5.51 0.10 0.0751 0.0003 1296 B 2.95
Auto0 110.60 ns 1.130 ns 1.302 ns 110.30 ns 1.90 0.04 0.0264 - 456 B 1.04
AutoFunc 289.80 ns 6.580 ns 7.313 ns 295.77 ns 4.98 0.15 0.0751 0.0003 1296 B 2.95
Poco 58.17 ns 1.031 ns 1.103 ns 58.17 ns 1.00 0.03 0.0255 - 440 B 1.00
Poco0 60.80 ns 0.176 ns 0.202 ns 60.73 ns 1.05 0.02 0.0227 - 392 B 0.89
PocoFunc 48.10 ns 1.059 ns 1.087 ns 49.06 ns 0.83 0.02 0.0255 - 440 B 1.00
  • Auto0是AutoMapper把Menu0列表转化为DTO的case
  • Poco0是Poco把Menu0列表转化为DTO的case
  • AutoMapper循环引用处理耗时和内存都是列表的3倍
  • Poco循环引用处理和列表性能差不多
  • 当然就算是无循环引用的列表处理,AutoMapper耗时也几乎是Poco的两倍
  • 这充分说明AutoMapper处理循环引用是有问题的

5. 先对比一下AutoMapper有无循环引用的代码

5.1 AutoMapper无循环引用的代码如下

T __f<T>(System.Func<T> f) => f();
(Func<List<Menu0>, List<Menu0DTO>, ResolutionContext, List<Menu0DTO>>)((List<Menu0> source, List<Menu0DTO> mapperDestination, ResolutionContext context) => //List<Menu0DTO>(source == null) ? new List<Menu0DTO>() : __f(() => {try{List<Menu0DTO> collectionDestination = null;List<Menu0DTO> passedDestination = null;passedDestination = mapperDestination;collectionDestination = passedDestination ?? new List<Menu0DTO>();collectionDestination.Clear();List<Menu0>.Enumerator enumerator = default;Menu0 item = null;enumerator = source.GetEnumerator();try{while (true){if (enumerator.MoveNext()){item = enumerator.Current;collectionDestination.Add(((Func<Menu0, Menu0DTO, ResolutionContext, Menu0DTO>)((Menu0 source_1, Menu0DTO destination, ResolutionContext context) => //Menu0DTO(source_1 == null) ? (destination == null) ? (Menu0DTO)null : destination : __f(() => {Menu0DTO typeMapDestination = null;typeMapDestination = destination ?? new Menu0DTO();try{typeMapDestination.ParentId = source_1.ParentId;}catch (Exception ex){throw TypeMapPlanBuilder.MemberMappingError(ex,default(PropertyMap)/*NOTE: Provide the non-default value for the Constant!*/);}try{typeMapDestination.Id = source_1.Id;}catch (Exception ex){throw TypeMapPlanBuilder.MemberMappingError(ex,default(PropertyMap)/*NOTE: Provide the non-default value for the Constant!*/);}try{typeMapDestination.Name = source_1.Name;}catch (Exception ex){throw TypeMapPlanBuilder.MemberMappingError(ex,default(PropertyMap)/*NOTE: Provide the non-default value for the Constant!*/);}try{typeMapDestination.Description = source_1.Description;}catch (Exception ex){throw TypeMapPlanBuilder.MemberMappingError(ex,default(PropertyMap)/*NOTE: Provide the non-default value for the Constant!*/);}return typeMapDestination;}))).Invoke(item,(Menu0DTO)null,context));}else{goto LoopBreak;}}LoopBreak:;}finally{enumerator.Dispose();}return collectionDestination;}catch (Exception ex){throw MapperConfiguration.GetMappingError(ex,default(MapRequest)/*NOTE: Provide the non-default value for the Constant!*/);}}));

5.2 AutoMapper循环引用的代码如下

5.2.1 Menu转MenuDTO
T __f<T>(System.Func<T> f) => f();
(Func<Menu, MenuDTO, ResolutionContext, MenuDTO>)((Menu source, MenuDTO destination, ResolutionContext context) => //MenuDTO(source == null) ? (destination == null) ? (MenuDTO)null : destination : __f(() => {MenuDTO typeMapDestination = null;ResolutionContext.CheckContext(ref context);return ((MenuDTO)context.GetDestination(source,typeof(MenuDTO))) ?? __f(() => {typeMapDestination = destination ?? new MenuDTO();context.CacheDestination(source,typeof(MenuDTO),typeMapDestination);typeMapDestination;try{typeMapDestination.Id = source.Id;}catch (Exception ex){throw TypeMapPlanBuilder.MemberMappingError(ex,default(PropertyMap)/*NOTE: Provide the non-default value for the Constant!*/);}try{typeMapDestination.Name = source.Name;}catch (Exception ex){throw TypeMapPlanBuilder.MemberMappingError(ex,default(PropertyMap)/*NOTE: Provide the non-default value for the Constant!*/);}try{typeMapDestination.Description = source.Description;}catch (Exception ex){throw TypeMapPlanBuilder.MemberMappingError(ex,default(PropertyMap)/*NOTE: Provide the non-default value for the Constant!*/);}try{List<Menu> resolvedValue = null;List<MenuDTO> mappedValue = null;resolvedValue = source.Children;mappedValue = (resolvedValue == null) ? new List<MenuDTO>() : context.MapInternal<List<Menu>, List<MenuDTO>>(resolvedValue,(destination == null) ? (List<MenuDTO>)null : typeMapDestination.Children,(MemberMap)default(PropertyMap)/*NOTE: Provide the non-default value for the Constant!*/);typeMapDestination.Children = mappedValue;}catch (Exception ex){throw TypeMapPlanBuilder.MemberMappingError(ex,default(PropertyMap)/*NOTE: Provide the non-default value for the Constant!*/);}return typeMapDestination;});}));
5.2.2 List<Menu>转List<MenuDTO>
T __f<T>(System.Func<T> f) => f();
(Func<List<Menu>, List<MenuDTO>, ResolutionContext, List<MenuDTO>>)((List<Menu> source,List<MenuDTO> mapperDestination,ResolutionContext context) => //List<MenuDTO>(source == null) ?new List<MenuDTO>() :__f(() => {try{List<MenuDTO> collectionDestination = null;List<MenuDTO> passedDestination = null;ResolutionContext.CheckContext(ref context);passedDestination = mapperDestination;collectionDestination = passedDestination ?? new List<MenuDTO>();collectionDestination.Clear();List<Menu>.Enumerator enumerator = default;Menu item = null;enumerator = source.GetEnumerator();try{while (true){if (enumerator.MoveNext()){item = enumerator.Current;collectionDestination.Add(((Func<Menu, MenuDTO, ResolutionContext, MenuDTO>)((Menu source_1,MenuDTO destination,ResolutionContext context) => //MenuDTO(source_1 == null) ?(destination == null) ? (MenuDTO)null : destination :__f(() => {MenuDTO typeMapDestination = null;ResolutionContext.CheckContext(ref context);return ((MenuDTO)context.GetDestination(source_1,typeof(MenuDTO))) ??__f(() => {typeMapDestination = destination ?? new MenuDTO();context.CacheDestination(source_1,typeof(MenuDTO),typeMapDestination);typeMapDestination;try{typeMapDestination.Id = source_1.Id;}catch (Exception ex){throw TypeMapPlanBuilder.MemberMappingError(ex,default(PropertyMap)/*NOTE: Provide the non-default value for the Constant!*/);}try{typeMapDestination.Name = source_1.Name;}catch (Exception ex){throw TypeMapPlanBuilder.MemberMappingError(ex,default(PropertyMap)/*NOTE: Provide the non-default value for the Constant!*/);}try{typeMapDestination.Description = source_1.Description;}catch (Exception ex){throw TypeMapPlanBuilder.MemberMappingError(ex,default(PropertyMap)/*NOTE: Provide the non-default value for the Constant!*/);}try{List<Menu> resolvedValue = null;List<MenuDTO> mappedValue = null;resolvedValue = source_1.Children;mappedValue = (resolvedValue == null) ?new List<MenuDTO>() :context.MapInternal<List<Menu>, List<MenuDTO>>(resolvedValue,(destination == null) ? (List<MenuDTO>)null :typeMapDestination.Children,(MemberMap)default(PropertyMap)/*NOTE: Provide the non-default value for the Constant!*/);typeMapDestination.Children = mappedValue;}catch (Exception ex){throw TypeMapPlanBuilder.MemberMappingError(ex,default(PropertyMap)/*NOTE: Provide the non-default value for the Constant!*/);}return typeMapDestination;});}))).Invoke(item,(MenuDTO)null,context));}else{goto LoopBreak;}}LoopBreak:;}finally{enumerator.Dispose();}return collectionDestination;}catch (Exception ex){throw MapperConfiguration.GetMappingError(ex,default(MapRequest)/*NOTE: Provide the non-default value for the Constant!*/);}}));

5.3 AutoMapper有无循环引用的代码分析如下

  • 循环引用的代码有2段,1段处理Menu,另1段处理List<Menu>
  • 直接对比处理List<Menu>部分
  • 很明显有循环引用部分多了不少特殊代码
5.3.1 AutoMapper循环引用多出以下代码
  • ResolutionContext.CheckContext消耗内存
  • context.GetDestination消耗内存和cpu
  • context.CacheDestination消耗内存和cpu
  • context.MapInternal用于调用代码
5.3.2 AutoMapper代码总结
  • MapInternal用于解决编译死循环的问题
  • GetDestination和CacheDestination用于解决执行死循环的问题
  • 但是这个case没有对象重复引用,没有执行死循环
  • 也就是说这里的GetDestination和CacheDestination只是消耗内存和cpu做无用功
  • 更让人无法接受的是,做这些无用功的消耗居然是正常代码的好几倍
  • 在无循环引用代码中ResolutionContext就是个摆设,无任何作用

6. 执行死循环该怎么处理呢

  • .net序列化给了我们答案
  • 序列化默认不支持对象循环引用,需要特殊配置,这是为了照顾大部分情况下的性能

6.1 序列化对象循环引用代码

Node node9 = new() { Id = 9, Name = "node9" };
Node node8 = new() { Id = 8, Name = "node8", Next = node9 };
Node node7 = new() { Id = 7, Name = "node7", Next = node8 };
Node node6 = new() { Id = 6, Name = "node6", Next = node7 };
Node node5 = new() { Id = 5, Name = "node5", Next = node6 };
Node node4 = new() { Id = 4, Name = "node4", Next = node5 };
Node node3 = new() { Id = 3, Name = "node3", Next = node4 };
Node node2 = new() { Id = 2, Name = "node2", Next = node3 };
Node node1 = new() { Id = 1, Name = "node1", Next = node2 };
node9.Next = node1; // 形成环
var referenceJson = JsonSerializer.Serialize(dto, new JsonSerializerOptions{ReferenceHandler = ReferenceHandler.Preserve,WriteIndented = true
});
referenceJson.Display();
  • 如果以上代码不配置ReferenceHandler会报错
  • 异常信息为A possible object cycle was detected...

7. 对比Poco循环引用处理的代码

7.1 Poco循环引用处理的代码如下

(Func<Menu, MenuDTO>)((Menu source) => //MenuDTO
{MenuDTO dest = null;if ((source != (Menu)null)){dest = new MenuDTO();List<Menu> Children = null;dest.Id = source.Id;dest.Name = source.Name;dest.Description = source.Description;Children = source.Children;if ((Children != null)){dest.Children = default(CompiledConverter<List<Menu>, List<MenuDTO>>)/*NOTE: Provide the non-default value for the Constant!*/.Convert(Children);}}return dest;
});

7.2 代码对比AutoMapper

  • AutoMapper生成代码量是Poco的3倍多
  • CompiledConverter.Convert对应AutoMapper的context.MapInternal
  • 本case中Poco无多余缓存处理,节省了大量cpu和内存
  • 如果有对象循环引用Poco该怎么办呢

三、再举个环形链表的Case

  • 链表是类型循环引用
  • 环形链表又是对象循环引用
  • 中国传统有九九归一的说法,以此为例

1. 九九归一代码

public class Node
{public int Id { get; set; }public string Name { get; set; }public Node Next { get; set; }public static Node GetNode(){Node node9 = new() { Id = 9, Name = "node9" };Node node8 = new() { Id = 8, Name = "node8", Next = node9 };Node node7 = new() { Id = 7, Name = "node7", Next = node8 };Node node6 = new() { Id = 6, Name = "node6", Next = node7 };Node node5 = new() { Id = 5, Name = "node5", Next = node6 };Node node4 = new() { Id = 4, Name = "node4", Next = node5 };Node node3 = new() { Id = 3, Name = "node3", Next = node4 };Node node2 = new() { Id = 2, Name = "node2", Next = node3 };Node node1 = new() { Id = 1, Name = "node1", Next = node2 };node9.Next = node1; // 形成环return node1;}
}

2. PocoEmit配置缓存解决对象循环引用问题

  • ComplexCached.Circle表示只有检测到循环引用才开启缓存
  • ComplexCached.Circle策略基本等同AutoMapper
  • 默认是ComplexCached.Never,不开启缓存
var node = Node.GetNode();
var manager = PocoEmit.Mapper.Create(new MapperOptions { Cached = ComplexCached.Circle });
var dto = manager.Convert<Node, NodeDTO>(node);

2.1 执行效果如下:

  • 以下测试是使用vscode执行的(需要Jupyter Notebook插件)
  • 测试代码地址为: https://github.com/donetsoftwork/MyEmit/tree/main/Notes/node.dib
  • gitee地址: https://gitee.com/donetsoftwork/MyEmit/tree/main/Notes/node.dib
{"$id": "1","Id": 1,"Name": "node1","Next": {"$id": "2","Id": 2,"Name": "node2","Next": {"$id": "3","Id": 3,"Name": "node3","Next": {"$id": "4","Id": 4,"Name": "node4","Next": {"$id": "5","Id": 5,"Name": "node5","Next": {"$id": "6","Id": 6,"Name": "node6","Next": {"$id": "7","Id": 7,"Name": "node7","Next": {"$id": "8","Id": 8,"Name": "node8","Next": {"$id": "9","Id": 9,"Name": "node9","Next": {"$ref": "1"}}}}}}}}}
}

2.2 与AutoMapper性能对比如下

Method Mean Error StdDev Median Ratio RatioSD Gen0 Gen1 Allocated Alloc Ratio
Auto 678.3 ns 12.65 ns 14.06 ns 666.1 ns 1.78 0.04 0.0936 0.0004 1616 B 4.49
AutoFunc 632.7 ns 4.18 ns 4.64 ns 628.8 ns 1.66 0.02 0.0936 0.0004 1616 B 4.49
Poco 381.8 ns 2.66 ns 3.07 ns 382.0 ns 1.00 0.01 0.0208 - 360 B 1.00
PocoFunc 365.4 ns 2.73 ns 2.92 ns 366.9 ns 0.96 0.01 0.0208 - 360 B 1.00
  • 首先可以看出Poco和AutoMapper执行耗时都挺高的
  • 所以建议大家使用AutoMapper尽量避免类型循环引用
  • 使用Poco也建议大家尽量避免对象循环引用
  • Poco性能好不少,差不多2倍
  • 内存分配上Poco优势更明显,AutoMapper分配了4倍多的内存

3. PocoEmit还可以通过GetContextConvertFunc来控制对象缓存

3.1 GetContextConvertFunc调用代码

  • GetContextConvertFunc是强制开启缓存,忽略mapper的缓存配置
  • 并设置当前类型必须缓存
var node = Node.GetNode();
var manager = PocoEmit.Mapper.Create();
Func<IConvertContext, Node, NodeDTO> contextFunc = manager.GetContextConvertFunc<Node, NodeDTO>();
using var context = SingleContext<Node, NodeDTO>.Pool.Get();
var dto = _pocoContextFunc(context, _node);
  • 需要特别强调,context是用来做缓存的,不是专门用来做处理循环引用的
  • 巧的是缓存能解决对象循环引用问题
  • 缓存除了处理对象循环引用当然还有其他用处

3.2 加入GetContextConvertFunc对比如下

Method Mean Error StdDev Median Ratio RatioSD Gen0 Gen1 Allocated Alloc Ratio
Auto 657.2 ns 8.03 ns 8.92 ns 664.7 ns 1.81 0.04 0.0936 0.0004 1616 B 4.49
AutoFunc 621.6 ns 8.09 ns 8.65 ns 614.4 ns 1.71 0.04 0.0936 0.0004 1616 B 4.49
Poco 363.5 ns 6.60 ns 7.60 ns 359.6 ns 1.00 0.03 0.0208 - 360 B 1.00
PocoFunc 349.1 ns 1.51 ns 1.74 ns 348.8 ns 0.96 0.02 0.0208 - 360 B 1.00
PocoContextFunc 350.8 ns 3.15 ns 3.63 ns 350.0 ns 0.97 0.02 0.0208 - 360 B 1.00
  • PocoContextFunc性能和GetConvertFunc差不多
  • 主要影响性能的是缓存的读写
  • 该方法通过暴露IConvertContext参数给自定义和配置提供了想象空间
  • 是不是可以通过实现IConvertContext来实现想要的逻辑和性能

四、重复引用非循环的Case

1. 一个突击小组的代码

  • 小组只有3人
  • 1人做队长
  • 1人做通讯员
public class SoldierTeam
{public Soldier Leader { get; set; }public Soldier Courier { get; set; }public List<Soldier> Members { get; set; }public static SoldierTeam GetTeam(){var leader = new Soldier { Name = "张三" };var courier = new Soldier { Name = "李四" };var other = new Soldier { Name = "王二" };var team = new SoldierTeam{Leader = leader,Courier = courier,Members = new List<Soldier>{leader,courier,other}};return team;}
}
public class Soldier
{public string Name { get; set; }
}

2. Poco默认情况下转化为5个对象

  • 这明显并不是用户想要的结果
  • AutoMapper可以通过PreserveReferences配置跟踪引用(就是缓存)
var manager = PocoEmit.Mapper.Create().UseCollection();
var team = SoldierTeam.GetTeam();
var dto = manager.Convert<SoldierTeam, SoldierTeamDTO>(team);
// dtoList.Length == 5
var dtoList = dto.Members.Concat([dto.Leader, dto.Courier]).Distinct().ToArray();

3. Poco配置缓存可以解决问题

  • ComplexCached.Always表示可能需要缓存就开启
  • 实际是检测有类被属性多次引用就开启缓存
  • 或有循环引用也开启缓存

3.1 Poco缓存转化代码

var manager = PocoEmit.Mapper.Create(new MapperOptions { Cached = ComplexCached.Always }).UseCollection();
var team = SoldierTeam.GetTeam();
var dto = manager.Convert<SoldierTeam, SoldierTeamDTO>(team);
// dtoList.Length == 3
var dtoList = dto.Members.Concat([dto.Leader, dto.Courier]).Distinct().ToArray();

3.2 Poco生成以下代码

T __f<T>(System.Func<T> f) => f();
(Func<SoldierTeam, SoldierTeamDTO>)((SoldierTeam source) => //SoldierTeamDTO
{SoldierTeamDTO dest = null;IConvertContext context = null;if ((source != (SoldierTeam)null)){context = ConvertContext.Create();if ((source != (SoldierTeam)null)){dest = new SoldierTeamDTO();context.SetCache<SoldierTeam, SoldierTeamDTO>(source,dest);Soldier Leader = null;Soldier Courier = null;List<Soldier> Members = null;Leader = source.Leader;if ((Leader != null)){// { The block result will be assigned to `dest.Leader`SoldierDTO dest_1 = null;dest.Leader = context.TryGetCache<Soldier, SoldierDTO>(Leader,out dest_1) ? dest_1 : __f(() => {SoldierDTO dest_2 = null;if ((Leader != (Soldier)null)){dest_2 = new SoldierDTO();context.SetCache<Soldier, SoldierDTO>(Leader,dest_2);dest_2.Name = Leader.Name;}return dest_2;});// } end of block assignment;}Courier = source.Courier;if ((Courier != null)){// { The block result will be assigned to `dest.Courier`SoldierDTO dest_3 = null;dest.Courier = context.TryGetCache<Soldier, SoldierDTO>(Courier,out dest_3) ? dest_3 : __f(() => {SoldierDTO dest_4 = null;if ((Courier != (Soldier)null)){dest_4 = new SoldierDTO();context.SetCache<Soldier, SoldierDTO>(Courier,dest_4);dest_4.Name = Courier.Name;}return dest_4;});// } end of block assignment;}Members = source.Members;if ((Members != null)){// { The block result will be assigned to `dest.Members`List<SoldierDTO> dest_5 = null;dest.Members = context.TryGetCache<List<Soldier>, List<SoldierDTO>>(Members,out dest_5) ? dest_5 : __f(() => {List<SoldierDTO> dest_6 = null;if ((Members != (List<Soldier>)null)){dest_6 = new List<SoldierDTO>(Members.Count);context.SetCache<List<Soldier>, List<SoldierDTO>>(Members,dest_6);int index = default;int len = default;index = 0;len = Members.Count;while (true){if ((index < len)){Soldier sourceItem = null;SoldierDTO destItem = null;sourceItem = Members[index];// { The block result will be assigned to `destItem`SoldierDTO dest_7 = null;destItem = context.TryGetCache<Soldier, SoldierDTO>(sourceItem,out dest_7) ? dest_7 : __f(() => {SoldierDTO dest_8 = null;if ((sourceItem != (Soldier)null)){dest_8 = new SoldierDTO();context.SetCache<Soldier, SoldierDTO>(sourceItem,dest_8);dest_8.Name = sourceItem.Name;}return dest_8;});// } end of block assignment;dest_6.Add(destItem);index++;}else{goto forLabel;}}forLabel:;}return dest_6;});// } end of block assignment;}}context.Dispose();}return dest;
});

4. Poco通过GetContextConvertFunc也可以处理

4.1 GetContextConvertFunc转化代码

var manager = PocoEmit.Mapper.Create().UseCollection();
var team = SoldierTeam.GetTeam();
Func<IConvertContext, SoldierTeam, SoldierTeamDTO> func = manager.GetContextConvertFunc<SoldierTeam, SoldierTeamDTO>();
using var context = SingleContext<Soldier, SoldierDTO>.Pool.Get();
var dto = func(context, team);
// dtoList.Length == 3
var dtoList = dto.Members.Concat([dto.Leader, dto.Courier]).Distinct().ToArray();

4.2 Poco生成以下代码

T __f<T>(System.Func<T> f) => f();
(Func<IConvertContext, SoldierTeam, SoldierTeamDTO>)((IConvertContext context, SoldierTeam source) => //SoldierTeamDTO
{SoldierTeamDTO dest = null;if ((source != (SoldierTeam)null)){dest = new SoldierTeamDTO();context.SetCache<SoldierTeam, SoldierTeamDTO>(source,dest);Soldier Leader = null;Soldier Courier = null;List<Soldier> Members = null;Leader = source.Leader;if ((Leader != null)){// { The block result will be assigned to `dest.Leader`SoldierDTO dest_1 = null;dest.Leader = context.TryGetCache<Soldier, SoldierDTO>(Leader,out dest_1) ? dest_1 : __f(() => {SoldierDTO dest_2 = null;if ((Leader != (Soldier)null)){dest_2 = new SoldierDTO();context.SetCache<Soldier, SoldierDTO>(Leader,dest_2);dest_2.Name = Leader.Name;}return dest_2;});// } end of block assignment;}Courier = source.Courier;if ((Courier != null)){// { The block result will be assigned to `dest.Courier`SoldierDTO dest_3 = null;dest.Courier = context.TryGetCache<Soldier, SoldierDTO>(Courier,out dest_3) ? dest_3 : __f(() => {SoldierDTO dest_4 = null;if ((Courier != (Soldier)null)){dest_4 = new SoldierDTO();context.SetCache<Soldier, SoldierDTO>(Courier,dest_4);dest_4.Name = Courier.Name;}return dest_4;});// } end of block assignment;}Members = source.Members;if ((Members != null)){// { The block result will be assigned to `dest.Members`List<SoldierDTO> dest_5 = null;dest.Members = context.TryGetCache<List<Soldier>, List<SoldierDTO>>(Members,out dest_5) ? dest_5 : __f(() => {List<SoldierDTO> dest_6 = null;if ((Members != (List<Soldier>)null)){dest_6 = new List<SoldierDTO>(Members.Count);context.SetCache<List<Soldier>, List<SoldierDTO>>(Members,dest_6);int index = default;int len = default;index = 0;len = Members.Count;while (true){if ((index < len)){Soldier sourceItem = null;SoldierDTO destItem = null;sourceItem = Members[index];// { The block result will be assigned to `destItem`SoldierDTO dest_7 = null;destItem = context.TryGetCache<Soldier, SoldierDTO>(sourceItem,out dest_7) ? dest_7 : __f(() => {SoldierDTO dest_8 = null;if ((sourceItem != (Soldier)null)){dest_8 = new SoldierDTO();context.SetCache<Soldier, SoldierDTO>(sourceItem,dest_8);dest_8.Name = sourceItem.Name;}return dest_8;});// } end of block assignment;dest_6.Add(destItem);index++;}else{goto forLabel;}}forLabel:;}return dest_6;});// } end of block assignment;}}return dest;
});

5. 性能测试如下

Method Mean Error StdDev Ratio RatioSD Gen0 Allocated Alloc Ratio
Auto 306.8 ns 10.60 ns 12.21 ns 1.39 0.06 0.0459 792 B 4.12
AutoFunc 259.1 ns 1.32 ns 1.47 ns 1.18 0.02 0.0459 792 B 4.12
Poco 220.2 ns 2.95 ns 3.39 ns 1.00 0.02 0.0111 192 B 1.00
PocoFunc 206.8 ns 2.26 ns 2.61 ns 0.94 0.02 0.0111 192 B 1.00
PocoContextFunc 207.4 ns 2.74 ns 3.15 ns 0.94 0.02 0.0111 192 B 1.00
  • PocoFunc性能和PocoContextFunc性能差不多
  • 如果喜欢隔离配置的同学,可以使用缓存配置方案
  • 如果喜欢集中配置的同学,可以使用GetContextConvertFunc
  • AutoMapper耗时1.4倍,内存占用4倍多

五、总结

1. 与AutoMapper处理循环引用的原理是一样的

  • 用其他对象调用,代替当时尚未编译的代码处理编译死循环
  • 使用缓存解决执行死循环
  • 缓存操作比原本对象转化耗时多太多,请大家慎用缓存

2. AutoMapper处理粗犷一点

  • 所有对象转化都加上下文对象,哪怕完全用不上
  • 检测到循环引用就加读写缓存,拖累到性能
  • 用了AutoMapper如果感觉获取数据慢,可以查一下是否有循环引用
  • 如果AutoMapper转化数据比实际数据库操作还慢也不要太过惊讶
  • 这里链接园内大佬的一篇文章: https://www.cnblogs.com/dudu/p/5863042.html

3. Poco处理就细致的多

  • 只有需要时才加上下文
  • 上下文来自内存池,用完回收复用,节约内存
  • 用户可以通过配置或GetContextConvertFunc选择性开启缓存
  • 自定义IConvertContext可以提供更多想象的空间
  • 另外无论是否开启缓存,Poco的性能都优于AutoMapper

另外源码托管地址: https://github.com/donetsoftwork/MyEmit ,欢迎大家直接查看源码。
gitee同步更新:https://gitee.com/donetsoftwork/MyEmit

如果大家喜欢请动动您发财的小手手帮忙点一下Star,谢谢!!!

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

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

相关文章

网站制作怎么学去哪学建设个人网站需要什么条件

摘要&#xff1a; 云托付&#xff08;Cloud Hosting&#xff09;是以阿里云的标准&#xff0c;提供给企业优质的机房托管资源、云专线网络以及增值服务&#xff0c;并与阿里云公有云产品结合&#xff0c;为企业搭建混合云提供基础资源。 原文地址&#xff1a;http://click.aliy…

JAVA 语法基础课程动手动脑及课后实验问题整理文档

一、编程思维与项目分解相关 1.按照 “分解 — 识别模式 — 抽象 — 算法” 的编程思维项目分解原则,重新编写课前测试题。思考将整个程序分成若干个组件(分解)、将多次出现的相似功能设成独立的方法(模式识别、抽…

python垃圾回收

python垃圾回收Python的垃圾回收机制以引⽤计数器为主、分代码回收和标记清除为辅 1.refchai链表 在Python的C源码中有⼀个名为refchain的环状双向链表,在Python程序中每创建1个对象,就会将其加入此链表。 city = 四…

Arduino IDE 离线更新ESP-32 lib包

前提:学习ESP-32过程中,没有科技,只能离线安装依赖; 1. 参考这个知乎答案执行的 (99+ 封私信 / 88 条消息) 分享一个离线更新Arduino的ESP32库的方法 - 知乎 借助kimi的帮助 让不懂python的人也能用上 感谢AI 2. …

上海企业注销流程seo发布专员招聘

Java内存模型可以说是Java并发的底层支持&#xff0c;了解Java内存模型才能正在了解Java并发。内存模型在内存中设置一个变量"value 1&#xff1b;"那么其他线程能在什么时候读取到这个结果呢&#xff1f;有可能不能立即甚至永远都读不到。比如指令顺序与源代码中的…

CUDA编程(CUDA_By_Example笔记)

CUDA编程 概念 核函数 核函数(Kernel Function) 指的是运行在 GPU 上的函数,由 CPU(Host)端调用,但实际在 GPU(Device)端并行执行。 核函数的声明需要限定符 __global__ ,例如: __global__ void add(int *a,…

K8S部署Openwebui 服务(Nvidia版)

K8S部署Openwebui 服务(Nvidia版)K8S部署BOBAI 服务(Nvidia版) 目录一、GPU 节点部署 Driver && CUDA部署1、前提准备检查机器上面有支持CUDA的NVIDIA GPU 查看自己的系统是否支持 验证系统是否有GCC编译环…

传统AI对话:悟空也辛苦(ai元人文)

悟空也辛苦 absolutely. 您这个补充至关重要,它点明了整个系统实现“智慧循环”和“责任追溯”的最后一环。我们把它整合进去。 这个“悟空中的数据备案”机制,可以称之为 “悟空的全程审计追踪” 或 “创造性过程档…

响应网站开发内容营销策略有哪些

Java异常架构与异常关键字Java异常简介Java异常架构1. Throwable2. Error&#xff08;错误&#xff09;3. Exception&#xff08;异常&#xff09;运行时异常编译时异常4. 受检异常与非受检异常受检异常非受检异常Java异常关键字Java异常处理声明异常抛出异常捕获异常如何选择异…

企业网站建设备案需要哪些资料广州网站制作公司电话

1. 注释Java中有三种注释&#xff1a;(1) // -单行注释&#xff0c;注释从“//”开始&#xff0c;终止于行尾&#xff1b;(2) -多行注释&#xff0c;注释从““结束&#xff1b;(3) -是Java特有的doc注释&#xff0c;这种注释主要是为支持JDK工具Javadoc而采用的。Javadoc能识…

广州个人网站制作apache 建立网站

阻塞式IO与非阻塞IO的区别 1. 阻塞式IO (Blocking I/O) 定义 当程序发起一个I/O操作&#xff08;如读取文件、网络数据&#xff09;时&#xff0c;进程会被挂起&#xff08;阻塞&#xff09;&#xff0c;直到操作完成或超时才会继续执行后续代码。在此期间&#xff0c;程序无法…

苍穹外卖-day01(软件开发整体介绍,苍穹外卖项目介绍,开发环境搭建,导入接口文档,Swagger) - a

苍穹外卖-day01(软件开发整体介绍,苍穹外卖项目介绍,开发环境搭建,导入接口文档,Swagger) 课程内容软件开发整体介绍 苍穹外卖项目介绍 开发环境搭建 导入接口文档 Swagger项目整体效果展示:​ …

做中东市场哪个网站合适海盐网站建设

1.概念 在现实生活中&#xff0c;可能存在一个与你一样的自己&#xff0c;我们称之为双胞胎。那在创建对象的时候&#xff0c;可否创建一个与已存在对象一模一样的新对象呢&#xff1f;答案是可以的&#xff0c;这就要通过拷贝构造函数来实现了。 拷贝构造函数&#xff1a;只有…

网页设计与网站建设基础中仑建设网站

调用地图接口展示数据库录入的不同类别地址信息&#xff0c;提供导航服务&#xff0c;手机端电脑端自适应。 语音介绍使用微软的tts接口可选不同语音性别生成

网站建设对企业的影响手机网站制作报价

为什么80%的码农都做不了架构师&#xff1f;>>> http://bbs.csdn.net/topics/340198955 android软键盘上推ui解决 good job 转载于:https://my.oschina.net/macleo/blog/204882

9.27动手动脑及课后实验

https://files.cnblogs.com/files/blogs/847689/动手动脑及课后实验.zip?t=1758987524&download=true

Combinatorics

[ICPC 2024 Nanjing R] Bingo 先给序列排序,权值相同的钦定标号前的更小。转化成 \(Ans\le a_k\) 的情况,等价于 \(k\) 个 \(1\),\(nm-k\) 个 \(0\) 放入 \(n\times m\) 的矩阵,至少有一行或者一列是全 \(1\)。考虑…

idea必备插件

1:gitToolBox————查看每行代码提交人 2:Translation————翻译插件 3:CheckStyle-IDEA————代码规范 4:Rainbow Brackets————彩虹括号 5:Nyan Progress Bar————可爱进度条 6:HighlightBracketP…

怎么做网站的点击率深圳公司网站设计

使用c语言如何统计单词个数发布时间&#xff1a;2020-04-21 13:58:58来源&#xff1a;亿速云阅读&#xff1a;207作者&#xff1a;小新使用c语言如何统计单词个数&#xff1f;相信有很多人都不太了解&#xff0c;今天小编为了让大家更加了解Golang&#xff0c;所以给大家总结了…