【问题标题】:Recursively iterating through object's properties throws StackOverflowException递归遍历对象的属性会引发 StackOverflowException
【发布时间】:2019-02-07 20:22:01
【问题描述】:

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

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 个类)。当深入了解GetProps(value 的对象时,我得到了StackOverflowException

有没有办法捕获异常并检查它失败的原因并解决问题?

编辑

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

if (visited.Contains(obj))
    return;

visited.Add(obj);

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

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

【问题讨论】:

  • 你应该查找访问者模式并且只遍历没有被访问过的对象类型。
  • 添加异常处理程序 try/catch。错误可能是您的内存不足。在代码运行时打开任务管理器。
  • @jdweng 对不起,但这两个帐户都是错误的。首先,正如@Daniel 在他的回答中指出的那样,您无法捕捉到您没有手动抛出的StackOverlflowException。其次,如果问题是进程内存不足,则会抛出OutOfMemoryException,现在是堆栈溢出。
  • 你可能甚至不需要在这里递归。请参阅 Eric Lippert 的通过堆栈遍历的方法:stackoverflow.com/a/20335369/1316856
  • string 不会也通过您的typeof(IEnumerable).IsAssignableFrom 条件吗?

标签: c# recursion reflection


【解决方案1】:

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

这很可能是因为您有一个循环引用。 IE。包含另一个对象的对象,该对象包含对原始对象的引用。两个类之间可以有任意数量的层次结构。

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

【讨论】:

  • 我没有循环引用,尽管我在图表的不同部分使用了类(尽管不是相同的实例)。
  • 堆栈溢出异常通常仅在数百次嵌套函数调用后发生。所以我很确定你有一个循环引用:-)
  • @Ivan-MarkDebono - 您不一定需要循环对象引用,这也可能发生在具有返回声明类型的属性的值类型上,例如DateTime.
  • @DanielHilgarth - 例如,DateTime 包含一个 Today 属性,该属性返回一个 DateTime,其中包含一个 Today 属性...
【解决方案2】:

这是一个不使用递归的示例,它利用了 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)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-03-19
    • 2020-12-06
    • 2012-01-08
    • 1970-01-01
    • 2014-08-09
    相关资源
    最近更新 更多