递归遍历对象的属性会引发StackOverflowException

时间:2018-09-02 15:43:30

标签: c# recursion reflection

我使用以下方法递归遍历对象的属性:

void GetProps(object obj)
{
    if (obj == null)
        return;

    var objType = obj.GetType();
    var properties = objType.GetProperties();

    foreach (var property in properties)
    {
        object value = property.GetValue(obj, null);

        if (typeof(IEnumerable).IsAssignableFrom(property.PropertyType))
        {
            var enumerable = (IEnumerable)value;

            foreach (object child in enumerable)
                GetProps(child);
        }
        else
        {
            GetProps(value);
        }
    }
}

对象非常复杂(超过30个类)。在StackOverflowException处深入对象时,我得到了GetProps(value

有没有一种方法可以捕获异常并检查异常原因,并解决问题?

编辑

我在方法顶部添加了一个故障保护:

if (visited.Contains(obj))
    return;

visited.Add(obj);

问题不在于循环引用(我没有),而在于诸如DateTimeintdecimal之类的属性类型。假设它们是原始的,但是IsPrimitive属性是false

我可以区分这些类型和自己的类吗?

2 个答案:

答案 0 :(得分:4)

A StackOverflowException can't be caught,除非您扔了它,因为它表明您的应用程序存在致命问题。

最有可能发生这种情况是因为您有一个循环引用。即一个包含另一个对象的对象,该对象包含对原始对象的引用。两个类之间可以有任意数量的层次结构级别。

您需要实现某种机制来停止遍历您已经遍历的对象,例如借助哈希集。

答案 1 :(得分:0)

以下是不使用递归的示例,该示例利用了Eric Lippert的explicit stack approach。我不知道字符串的行为是否符合您的预期,但这可能会阻止堆栈崩溃:

public static IEnumerable<object> GetPropertiesDepthFirst(object obj)
{
    if (obj == null)
        yield break;

    var stack = new Stack<object>();
    stack.Push(obj);

    while (stack.Count > 0)
    {
        var current = stack.Pop();
        yield return current;

        var objType = current.GetType();
        var properties = objType.GetProperties();

        foreach (var property in properties)
        {
            object value = property.GetValue(current, null);
            if (value == null)
                continue;

            if (typeof(IEnumerable).IsAssignableFrom(property.PropertyType))
            {
                var enumerable = (IEnumerable)value;
                foreach (object child in enumerable)
                    stack.Push(child);
            }
            else
            {
                yield return value;
            }
        }
    }
}

对于具有已定义索引的IEnumerables,您可能还希望排除索引属性:

objType.GetProperties().Where(p => p.GetIndexParameters().Length == 0)