You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C#递归遍历复杂对象结构时如何处理可枚举集合类型

递归遍历复杂对象属性提取值的实现方案

核心问题解答

  • 可枚举/集合类型识别逻辑
    不要用「命名空间是否以System.开头」作为递归判断条件,这个逻辑鲁棒性极差。集合类型的标准判断方式:
    1. 优先排除string类型:string本质实现了IEnumerable<char>,但业务场景下不需要遍历字符串的单个字符
    2. 判断目标类型是否实现System.Collections.IEnumerable接口,符合条件的即为可遍历集合类型
    3. 泛型集合(如List<T>、ICollection<T>)取泛型参数T作为集合元素类型递归,非泛型集合(如ArrayList)元素类型按object处理,遍历每个元素时取运行时实际类型递归。
  • 集合遍历未命中的回退逻辑
    不需要单独编写回退逻辑。采用深度优先递归规则即可:遍历到集合时逐个遍历内部元素,对每个元素递归执行属性查找;如果当前集合所有元素的属性树遍历完成仍未找到目标属性,递归方法会自动返回到父级节点,继续遍历父级剩余的其他属性。建议给递归方法设置返回值,找到目标值后立刻逐层返回终止遍历,减少无意义的性能损耗。
  • 需额外兼容的复杂类型
    原逻辑仅判断IsClass会遗漏大量场景,需要补充兼容:
    1. 自定义结构体(struct):值类型不满足IsClass判断,但结构体内部同样可能嵌套自定义属性,需要纳入递归范围
    2. 可空值类型(Nullable<T>):需要先通过Nullable.GetUnderlyingType(type)取到底层实际类型,再判断是否需要继续递归
    3. 字典类型(IDictionary/IDictionary<TKey,TValue>):常规业务场景下只需要遍历字典Value部分的属性,不需要遍历Key
    4. 数组类型:本质属于可枚举类型,可被集合判断逻辑覆盖,注意区分交错数组和多维数组的遍历差异即可
    5. 必须增加循环引用防护:业务对象经常存在双向引用(如用户对象关联部门属性,部门对象包含用户列表属性),不做防护会触发无限递归导致栈溢出,需要通过哈希集合记录已经遍历过的对象实例引用,遇到重复引用直接跳过。
      同时需要明确递归终止边界:遇到基元类型(int、bool、long等)、DateTime、decimal、Guid、byte[]这类无需拆解的系统内置类型,直接终止递归即可。

修正后的可直接运行的实现代码

原实现仅传入Type参数只能遍历类型定义结构,无法获取运行时属性值、也无法遍历集合内实际元素,以下实现直接传入对象实例,支持提取实际属性值:

using System.Collections;
using System.Reflection;
using System.Runtime.CompilerServices;

public static object FindPropertyValue(object source, string targetPropertyName, HashSet<object> visitedInstances = null)
{
    if (source == null) return null;
    // 初始化循环引用防护集合
    visitedInstances ??= new HashSet<object>(new ReferenceEqualityComparer());
    if (!visitedInstances.Add(source)) return null; // 已遍历过的实例直接跳过,避免死循环

    Type sourceType = source.GetType();
    // 遇到不需要拆解的基础类型,直接终止递归
    if (IsTerminateType(sourceType)) return null;

    // 优先处理字典类型
    if (source is IDictionary dictionary)
    {
        foreach (DictionaryEntry entry in dictionary)
        {
            var findInValue = FindPropertyValue(entry.Value, targetPropertyName, visitedInstances);
            if (findInValue != null) return findInValue;
        }
        return null;
    }

    // 处理其他集合类型(排除字符串)
    if (source is IEnumerable enumerable && source is not string)
    {
        foreach (var item in enumerable)
        {
            var findInItem = FindPropertyValue(item, targetPropertyName, visitedInstances);
            if (findInItem != null) return findInItem;
        }
        return null;
    }

    // 遍历当前对象所有公开实例属性
    PropertyInfo[] properties = sourceType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
    foreach (PropertyInfo prop in properties)
    {
        // 命中目标属性直接返回值,属性名匹配忽略大小写
        if (prop.Name.Equals(targetPropertyName, StringComparison.OrdinalIgnoreCase))
        {
            return prop.GetValue(source);
        }
        // 递归遍历当前属性的值
        var propValue = prop.GetValue(source);
        var findInProp = FindPropertyValue(propValue, targetPropertyName, visitedInstances);
        if (findInProp != null) return findInProp;
    }

    return null;
}

// 判断是否为不需要继续递归的终止类型
private static bool IsTerminateType(Type type)
{
    if (type.IsPrimitive || type == typeof(string) || type == typeof(DateTime) 
        || type == typeof(decimal) || type == typeof(Guid) || type == typeof(DateTimeOffset)
        || type == typeof(byte[]))
    {
        return true;
    }
    // 可空值类型如果底层是基元类型也终止递归
    Type underlyingType = Nullable.GetUnderlyingType(type);
    if (underlyingType != null)
    {
        return IsTerminateType(underlyingType);
    }
    return false;
}

// 旧版本.NET 自定义引用相等比较器,.NET 5+可直接用内置ReferenceEqualityComparer替换
private class ReferenceEqualityComparer : IEqualityComparer<object>
{
    public new bool Equals(object x, object y) => ReferenceEquals(x, y);
    public int GetHashCode(object obj) => RuntimeHelpers.GetHashCode(obj);
}

调用说明:直接传入根对象实例和目标属性名即可,方法会自动递归所有嵌套层级、集合、字典、结构体类型,找到第一个匹配的属性值就返回,未找到返回null。如果需要获取所有匹配的属性值,把返回值改为List<object>,找到值时加入列表不提前返回即可。

内容的提问来源于stack exchange,提问作者BatmanPriddy

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 00:24:43