C#中如何将处理ExpandoObject的递归DTO方法改造为迭代实现避免StackOverflow
递归方法转迭代实现的正确方案
核心问题定位
之前自行实现的迭代版本逻辑错位的根因是:没有为每个待处理的节点建立和父容器的直接绑定关系,依赖全局字典回溯查找挂载位置的方式,在多层嵌套、集合内嵌套的树状场景下很容易出现父级引用错位,最终导致输出结构和预期不符。
和原递归逻辑完全等价的迭代实现,必须做到每一个待处理的任务单元,都自带三类关联信息,完全不需要靠全局变量回溯:
- 当前待遍历的源字典/ExpandoObject节点
- 节点处理完成后,结果需要挂载到的父容器(可以是父字典,也可以是集合列表)
- 挂载时使用的键名/列表位置标记
正确实现代码
以下实现从根源避免栈溢出风险,输出结果和原递归方法的正确行为完全对齐,同时修正了原递归方法中处理集合时循环覆盖值的逻辑疏漏:
private IDictionary<string, object> AsDtoObjIterative(IDictionary<string, object> sourceDict) { // 定义栈处理单元:待处理源节点、结果挂载的父容器、挂载时使用的键 var processingStack = new Stack<( IDictionary<string, object> SourceNode, object ParentContainer, string MountKey )>(); // 初始化根结果字典 var rootResult = new Dictionary<string, object>(); // 压入根节点处理任务(根节点无父容器,直接作为最终返回值) processingStack.Push((sourceDict, rootResult, null)); while (processingStack.Count > 0) { var (currentSource, parentContainer, mountKey) = processingStack.Pop(); // 初始化当前节点对应的结果字典 var currentNodeResult = new Dictionary<string, object>(); // 提前把当前结果挂载到父容器,后续处理子节点时不需要回溯找位置 switch (parentContainer) { case IDictionary<string, object> parentDict: parentDict[mountKey] = currentNodeResult; break; case IList parentList: parentList[parentList.Count - 1] = currentNodeResult; break; } // 遍历当前源节点的所有键值对,逻辑和原递归完全对齐 foreach (var kv in currentSource) { var key = kv.Key; var value = kv.Value; if (value == null) continue; switch (value) { // 普通值直接写入当前结果字典 default: currentNodeResult[key] = value; break; // 嵌套字典/ExpandoObject,压栈后续处理 case IDictionary<string, object> nestedDict: processingStack.Push((nestedDict, currentNodeResult, key)); break; // 集合类型 case ICollection collection: // 初始化可修改的列表作为集合的转换结果 var convertedItemList = new List<object>(); // 把列表提前挂载到当前节点的对应键下 currentNodeResult[key] = convertedItemList; foreach (var item in collection) { if (item == null) { convertedItemList.Add(null); continue; } // 集合内的字典/ExpandoObject元素,压栈处理 if (item is IDictionary<string, object> itemDict) { convertedItemList.Add(null); // 先占位,后续替换为处理好的字典对象 processingStack.Push((itemDict, convertedItemList, null)); } else { // 集合内的普通值直接加入列表 convertedItemList.Add(item); } } break; } } } return rootResult; }
实现逻辑优势
- 所有节点的挂载关系在入栈时就已经绑定,哪怕嵌套层级达到上千层,也不会出现找错父级的问题,从根源上规避StackOverflow异常
- 处理顺序和原递归的深度优先遍历逻辑完全一致,最终输出的字典结构、值的类型和原递归的正确结果1:1对齐
- 所有结果字典提前初始化、提前挂载,不存在等所有子节点处理完再回头赋值的回溯操作,树状分支结构不会出现错位
原自定义实现的缺陷说明
之前编写的迭代版本存在两个核心逻辑问题:
- 栈单元仅存储待处理的字典和对应键,没有绑定挂载的父容器,处理嵌套节点时只能靠全局字典、取最后一个键的硬编码逻辑找父级,遇到多层嵌套、集合内嵌套的场景就会把值写到错误的位置
- 处理流程是“先解析子节点,再回头查找父级完成赋值”,和递归“先拿到父容器,子节点处理完成后直接挂载到父容器”的流程完全相反,多分支树状结构的处理顺序完全混乱
内容的提问来源于stack exchange,提问作者KJanek
相关产品推荐
相关产品推荐

