0

I am having a few issues finding a simple way to find all string values using Reflection on an object.

I need to scan an object recursively including any lists, arrays for a string that matches a value. So far I am trying to return a list of all the values then I can check that list, however I have run into a problem where this can throw a StackOverflowException if the object contains any circular dependencies.

What I have so far: (I know, I know, but its a work in progress :))

private IEnumerable<string> FindStringValues(object obj)
{
    if (obj != null)
    {
        Type type = obj.GetType();
        if (type == typeof(string))
        {
            yield return (string)obj;
        }
        else if (type.IsArray)
        {
            var array = obj as Array;
            foreach (var item in array)
            {
                foreach (var str in FindStringValues(item))
                {
                    yield return str;
                }
            }
        }
        else if (type.IsClass)
        {
            FieldInfo[] fields = type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
            foreach (FieldInfo field in fields)
            {
                object fieldValue = field.GetValue(obj);
                if (fieldValue == null)
                    continue;

                foreach (var str in FindStringValues(fieldValue))
                {
                    yield return str;
                }
            }
        }
    }
}

So I'm trying to find a way to make this StackOverflowException safe, even better if anyone can help me figure out how to make this a bool to return if a match is found with a supplied value.


EDIT:

Thanks to the answer from nsinreal I was able to accomplish the goal :) added below incase its helpful to others

public static bool FindStringValue(object obj, string valueToFind)
{
    return FindStringValue(obj, valueToFind, new List<object>());
}

private static bool FindStringValue(object obj, string valueToFind, IList<object> visitedObjects)
{
    if (obj == null && !visitedObjects.Any(item => Object.ReferenceEquals(item, obj)))
    {
        if (!(obj is string))
        {
            visitedObjects.Add(obj);
        }

        Type type = obj.GetType();
        if (type == typeof(string))
        {
            return (obj).ToString() == valueToFind;
        }

        if (typeof(IEnumerable).IsAssignableFrom(type))
        {
            var array = obj as IEnumerable;
            foreach (var item in array)
            {
                if (FindStringValue(item, valueToFind, visitedObjects))
                {
                    return true;
                }
            }
            return false;
        }

        if (type.IsClass)
        {
            FieldInfo[] fields = type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
            foreach (FieldInfo field in fields)
            {
                object item = field.GetValue(obj);
                if (item == null)
                    continue;

                if (FindStringValue(item, valueToFind, visitedObjects))
                {
                    return true;
                }
            }
        }
    }
    return false;
}
sa_ddam213
  • 42,848
  • 7
  • 101
  • 110
  • (To your edit) I wouldn't use `type` in the beginning. Instead of `if (type == typeof(string)) { return (obj).ToString() == valueToFind; }` I would say `if (obj as string == valueToFind) { return true; }`. Instead of the `IsAssignableFrom` part, I would go `var enumerable = obj as IEnumerable; /* first, and then */ if (enumerable != null) { /* foreach here */ }`. Just some suggestions. By the way, when you go to the reflection part, why do you restrict yourself to `IsClass`? Don't you want to search structs? – Jeppe Stig Nielsen Jul 08 '13 at 10:02
  • @JeppeStigNielsen, thanks for the input, I am currently refactoring and will take your input into accouut, as it stands now this is needed for a specific part in my application and Structs are not needed, the string will ether be directly in the object or in a object in a list of that object. Thanks. – sa_ddam213 Jul 08 '13 at 10:11

1 Answers1

4
  1. Any lists, any array -> any IEnumerable. So you can replace:

    if (type.IsArray)
    

    To:

    if (typeof(IEnumerable).IsAssignableFrom(type))
    
  2. To prevent StackOverflowException just don't visit visited objects. Do you like idea? So, firstly we must choose: we don't want visit all visited objects or only parent objects. In this case, we won't visit all visited objects. So, modifcate header:

    // visitedObjects can changes
    private static IEnumerable<string> _FindStringValues(object obj, IList<object> visitedObjects)
    

    Now, modificate all calls of FindStringValues to call of _FindStringValues in this method:

    _FindStringValues(item, visitedObjects)
    

    And add usable wrapper-method:

    private static IEnumerable<string> FindStringValues(object obj) {
        return _FindStringValues(obj, new List<object>());
    }
    

    In _FindStringValues you must add each obj passed into this method to visited objects list.

    visitedObjects.Add(obj);
    

    But before that you must check that this object wasn't visited. You must use Object.ReferenceEquals:

    if (visitedObjects.Any(item => Object.ReferenceEquals(item, obj)))
        yield break;
    

    Be carefully, if you want to see equal strings from diffrent objects you must prevent adding strings to visited objects list. Why? String interning. Like this:

    if (!(obj is string))
        visitedObjects.Add(obj);
    

After some code formating I have got this: code.

Community
  • 1
  • 1
Viktor Lova
  • 4,776
  • 2
  • 19
  • 25
  • Just addition - if performance is an issue, I would consider using Stack<> structure instead of calling _FindStringValues recurrently. – Ondrej Svejdar Jul 08 '13 at 08:44
  • @nsinreal, Thank you very much for the assistance, I was able to create the match function based on your answer above :), I have added it to my answer but if you think it can be improved feel free to comment. Thanks – sa_ddam213 Jul 08 '13 at 09:40