1

Given two .NET objects (a root and a leaf) in an arbitrary object graph (linked by properties and collections), is there an existing API or example to construct a path (something like a WPF property binding path, or XML XPath) to get from one to the other? The "source" (ie. the object which wants to find out the path), will be the leaf object.

Indexed locations must also be supported. (eg. Foo.Bar[42].Baz["frog"].Quux).

This is mainly intended for error reporting -- I would like to let objects log an error showing where they are in the object model, rather than just by their type name. (This is important because the same type of object might be contained by a large number of other object types, and the user action required to fix any issues will vary depending on that location.)

I can hand-roll something which does the trick by giving each object a reference to its parent and recursively asking each parent how to get to the child. But before I go do that I was wondering if there were any existing/better solutions. (And this is fragile if someone forgets to update the parent link, or if one object can be reached by two different paths, although that should be rare.)

Miral
  • 12,637
  • 4
  • 53
  • 93
  • might be a duplicate of this http://stackoverflow.com/questions/66893/tree-data-structure-in-c-sharp – Illuminati Nov 17 '11 at 06:01
  • 1
    Your solution would be the way to go. Most graph path impl. work that way (although it can also be the other way around). I'm not sure what the problem is with one object being able to be reached by two different paths, that is natural behavior in a graph and does have no consequences for a Path. Regarding updating: You can track a version number (int) in a node and each time you modify the node (child collection), increase the version number by one. Now when building a path you can copy the current version number and when traversing, you can simply check the version number for update – Polity Nov 17 '11 at 06:21
  • Bumble Bee: No, I don't think so. The links between objects are just regular named .NET properties. It's a sort of hierarchical entity simulation model, if that helps. :) – Miral Nov 17 '11 at 06:30
  • Polity: the only real issue with multiple incoming paths is that each object would need to store a collection of all parents, and then you'd end up with a collection of resulting paths, instead of having something nice and unambiguous. But that's probably unavoidable. I'm not sure how adding a version number would help, though -- that's just adding one more thing to forget to update. – Miral Nov 17 '11 at 06:34

2 Answers2

0

I have not seen anything close in the Framework.

Without "parent" property I think you looking for "shortest path in a graph" ( http://en.wikipedia.org/wiki/Pathfinding ), but it does not look like a good option for tracing/error logging due complexity and memory requirements to keep the path structures.

Alexei Levenkov
  • 98,904
  • 14
  • 127
  • 179
0

This is some very simplified variant of how you can start, hope this will help...

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;

namespace ConsoleApplication2
{
    class Test2
    {
        public bool BoolProp { get; set; }

        public int[] ArrayProp { get; set; }
    }

    class Test1
    {
        public string StringProp { get; set; }

        public Dictionary<string, Test2> DictionaryProp { get; set; }
    }

    class Program
    {


        private static string ObjToPathKey(object key)
        {
            if (key == null) return "null";
            if (key is string) return string.Format("\"{0}\"", key);
            return key.ToString();
        }

        public static IEnumerable<KeyValuePair<string, object>> GetFullTree(object root, string currentPath)
        {
            if (root == null) yield break;

            yield return new KeyValuePair<string, object>(currentPath, root);

            var type = root.GetType();
            if (!type.IsClass || type == typeof(string)) yield break;

            if (root is IEnumerable)
            {
                if (root is IDictionary)
                {
                    IDictionary d = (IDictionary) root;
                    foreach (var key in d.Keys)
                    {
                        var child = d[key];
                        foreach (var subchildPair in GetFullTree(child, string.Format("{0}[{1}]", currentPath, ObjToPathKey(key))))
                        {
                            yield return subchildPair;
                        }
                    }
                    yield break;
                }

                int i = 0;
                if (root is IList)
                {
                    foreach (var child in (IEnumerable)root)
                    {
                        foreach (var subChildPair in GetFullTree(child, string.Format("{0}[{1}]", currentPath, i)))
                        {
                            yield return subChildPair;
                        }
                        ++i;
                    }
                    yield break;
                }

                throw new NotSupportedException();
            }

            if (type.IsClass)
            {
                foreach (PropertyInfo propertyInfo in type.GetProperties())
                {
                    //TODO: Add indexers support
                    object propertyValue = propertyInfo.GetValue(root, null);
                    foreach (var subChildPair in GetFullTree(propertyValue, string.Format("{0}.{1}", currentPath, propertyInfo.Name)))
                    {
                        yield return subChildPair;
                    }
                }
            }
        }

        static void Main(string[] args)
        {
            Test1 t = new Test1()
                          {
                              StringProp = "Some value",
                              DictionaryProp = new Dictionary<string, Test2>()
                                                   {
                                                       {
                                                           "key1", new Test2()
                                                                       {
                                                                           ArrayProp = new[] {1, 2, 3},
                                                                           BoolProp = true
                                                                       }
                                                           },
                                                       {
                                                           "key 2", new Test2()
                                                                        {
                                                                            ArrayProp = new[] {4, 5, 6, 7},
                                                                            BoolProp = false
                                                                        }
                                                           }
                                                   }
                          };

            foreach (var pair in GetFullTree(t, "t"))
            {
                Console.WriteLine("{0} = {1}", pair.Key, pair.Value);
            }

            Console.Read();

            /* Program output:
                t = ConsoleApplication2.Test1
                t.StringProp = Some value
                t.DictionaryProp = System.Collections.Generic.Dictionary`2[System.String,Console
                Application2.Test2]
                t.DictionaryProp["key1"] = ConsoleApplication2.Test2
                t.DictionaryProp["key1"].BoolProp = True
                t.DictionaryProp["key1"].ArrayProp = System.Int32[]
                t.DictionaryProp["key1"].ArrayProp[0] = 1
                t.DictionaryProp["key1"].ArrayProp[1] = 2
                t.DictionaryProp["key1"].ArrayProp[2] = 3
                t.DictionaryProp["key 2"] = ConsoleApplication2.Test2
                t.DictionaryProp["key 2"].BoolProp = False
                t.DictionaryProp["key 2"].ArrayProp = System.Int32[]
                t.DictionaryProp["key 2"].ArrayProp[0] = 4
                t.DictionaryProp["key 2"].ArrayProp[1] = 5
                t.DictionaryProp["key 2"].ArrayProp[2] = 6
                t.DictionaryProp["key 2"].ArrayProp[3] = 7
             */
        }
    }
}
  • To prevent of infinity recursion, add a Stack argument into the GetFullTree(...) method, and track all links. To get a path for an exact child, use .FirstOrDefault(x => x.Value == targetObject). And remember, this functions IS VERY SLOW, so don't use it in critical code – Alexander Mavrinsky Nov 17 '11 at 07:00
  • I ended up doing it as I originally described: adding a list of parents to each node (via a common base class) and used a metaproperty system (similar to DependencyProperties) for fast access to the links between objects. But I'm ticking this solution since it provides code that looks like it'd work given one interpretation of the original problem, despite some performance issues from using reflection. – Miral Dec 01 '11 at 00:31