1

I'm recursively iterating through an object's properties using the following method:

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);
        }
    }
}

The object is very complex (over 30 classes). I'm getting a StackOverflowException when going deeper into the object at GetProps(value.

Is there a way to catch the exception and check why it's failing, and, solve the problem?

EDIT

I added a fail-safe at the top of the method:

if (visited.Contains(obj))
    return;

visited.Add(obj);

The problem is not with circular references (which I don't have) but with property types such as DateTime, int and decimal. which was assuming they're primitive but the IsPrimitive property is false.

Can I differentiate between such types and my own classes?

Parrish Husband
  • 3,148
  • 18
  • 40
Ivan-Mark Debono
  • 15,500
  • 29
  • 132
  • 263
  • You should look up the Visitor pattern and only traverse object types that have not been visited. – Ron Beyer Sep 02 '18 at 15:47
  • Add an exception handlers try/catch. Error is probably you are running out of memory. Open Task manager while code is running. – jdweng Sep 02 '18 at 15:48
  • 1
    @jdweng Sorry but that is wrong on both accounts. First, as @Daniel indicates in his answer, you can not catch a `StackOverlflowException` that you did not maunally throw. Second, if the issue was that the process is running out of memory, then an `OutOfMemoryException` would be thrown, now a stack overflow. – Rotem Sep 02 '18 at 15:52
  • 1
    You likely don't even need recursion here. See Eric Lippert's approach to traversing via stack: https://stackoverflow.com/a/20335369/1316856 – Parrish Husband Sep 02 '18 at 15:53
  • Wouldn't a `string` also pass your `typeof(IEnumerable).IsAssignableFrom` condition? – Parrish Husband Sep 02 '18 at 16:30
  • Is your intention to traverse the transitive closure of all the *types* of the properties, or all the *values* of the properties? Because there is no guarantee that either is finite, but there are ways to make the former terminate. There are some subtleties here. For example, consider `class C { public C> CCT => new C>(); }` If you have a `C` in hand then the value of `c.CCT` is a `C>`, and the value of *its* CCT is a `C>>` and so on. – Eric Lippert Sep 04 '18 at 17:37

2 Answers2

4

A StackOverflowException can't be caught unless you threw it, because it indicates a fatal problem with your application.

Most likely this happens because you have a circular reference. I.e. an object that contains another object which contains a reference to the original object. There may be an arbitrary number of hierarchy levels between the two classes.

You need to implement some kind of mechanism to stop traversing an object that you already traversed, e.g. with the help of a hash set.

Daniel Hilgarth
  • 171,043
  • 40
  • 335
  • 443
  • I don't have circular references although I have classes used in different parts of the graph (though not same instances). – Ivan-Mark Debono Sep 02 '18 at 15:48
  • 1
    A stackoverflow exception usually happens only after hundreds of nested function calls. So I am pretty sure that you have a circular reference :-) – Daniel Hilgarth Sep 02 '18 at 15:50
  • 3
    @Ivan-MarkDebono - You don't need necessarily circular object references, this could also happen with a value type with a property returning the declaring type e.g. `DateTime`. – Lee Sep 02 '18 at 16:00
  • 1
    @DanielHilgarth - For example `DateTime` contains a `Today` property which returns a `DateTime` which contains a `Today` property... – Lee Sep 02 '18 at 16:09
0

Here is an example not using recursion, which leverages Eric Lippert's explicit stack approach. I don't know if the behavior with strings is as you intended, but this may prevent your stack from blowing:

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;
            }
        }
    }
}

Also for IEnumerables with defined indexes, you may want to exclude Indexed Properties:

objType.GetProperties().Where(p => p.GetIndexParameters().Length == 0)
Parrish Husband
  • 3,148
  • 18
  • 40