Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to iterate a C# object, looking for all instances of a specific type, in order to build a separate list of those instances?

I have a need that is a bit similar to this question, except that it requires a deeper exploration of the source object.

Here is a code sample:

public class Target {};

public class Analyzed
{
    public Target EasyOne { get; set; }
    public IList<Target> ABitMoreTricky { get; set; }
    public IList<Tuple<string, Target>> Nightmare { get; set; }
}

From an instance of Analyzed, I want to extract all the Target instances.

In order to ease the exploration, we can assume the following:

  1. Explore only properties.
  2. There is no infinite reference loop.

For now, EasyOne is... easy, but I am looking for some strategy to get all the Target instances lost in more tricky structures.

like image 663
remio Avatar asked Dec 13 '22 04:12

remio


2 Answers

How about something along these lines:

    public List<T> FindAllInstances<T>(object value) where T : class
    {

        HashSet<object> exploredObjects = new HashSet<object>();
        List<T> found = new List<T>();

        FindAllInstances(value, exploredObjects, found);

        return found;
    }

    private void FindAllInstances<T>(object value, HashSet<object> exploredObjects, List<T> found) where T : class
    {
        if (value == null)
            return;

        if (exploredObjects.Contains(value))
            return;

        exploredObjects.Add(value);

        IEnumerable enumerable = value as IEnumerable;

        if (enumerable != null)
        {
            foreach(object item in enumerable)
            {
                FindAllInstances<T>(item, exploredObjects, found);
            }
        }
        else
        {
            T possibleMatch = value as T;

            if (possibleMatch != null)
            {
                found.Add(possibleMatch);
            }

            Type type = value.GetType();

            PropertyInfo[] properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.GetProperty);

            foreach(PropertyInfo property in properties)
            {
                object propertyValue = property.GetValue(value, null);

                FindAllInstances<T>(propertyValue, exploredObjects, found);
            }

        }

    private void TestIt()
    {
        Analyzed analyzed = new Analyzed()
        {
            EasyOne = new Target(),
            ABitMoreTricky = new List<Target>() { new Target() },
            Nightmare = new List<Tuple<string, Target>>() { new Tuple<string, Target>("", new Target()) }
        };

        List<Target> found = FindAllInstances<Target>(analyzed);

        MessageBox.Show(found.Count.ToString());
    }
like image 93
RQDQ Avatar answered May 06 '23 07:05

RQDQ


You could go the reflection way, and have special treatment for all the containers you know (IEnumerable, IDictionary, all Tuples, and who knows what else), or you can actually implement what @Adrian Iftode jokingly said in a comment.

I don't think you really want to serialize to XML and then parse it. It will work, but it will require all your objects to be XML serializable, which, if I am not mistaken, requires all serialized data to be public.

You should use the ordinary serialization, but define your own custom formatter that does nothing but track the objects you're looking for. Here's an example of a simple custom formatter.

like image 45
zmbq Avatar answered May 06 '23 07:05

zmbq