You need to enable JavaScript to run this app. C#中如何将处理ExpandoObject的递归DTO方法改造为迭代实现避免StackOverflow
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.11.05 19:54:08