Search code examples
c#parsingreflectionpropertyinfofieldinfo

Parse field/property path?


So I wrote this code that could parse a property path from a start object, returns the wanted property and it takes an out param for the source object for which the returned property could be invoked upon:

    public static PropertyInfo GetProperty(string path, object start, out object source)
    {
        if (string.IsNullOrEmpty(path))
            throw new ArgumentException();

        source = start;
        var pType = source.GetType();
        var paths = path.Split('.');

        PropertyInfo pInfo = null;
        for (int i = 0; i < paths.Length; i++) {
            var subpath = paths[i];
            pInfo = pType.GetProperty(subpath);
            if (i < paths.Length - 1) { // wonder if there's a better way writing this to avoid this if?
                source = pInfo.GetValue(source);
                pType = source.GetType();
            }
        }
        return pInfo;
    }

Now let's say I have the following hierarchy:

    public class Object
    {
        public string Name { get; set; }
    }
    public class GameObject : Object { }
    public class Component : Object
    {
        public GameObject gameObject { get; set; }
    }
    public class MonoBehaviour : Component { }
    public class Player : MonoBehaviour { }
    public class GameManager : MonoBehaviour
    {
        public Player player { get; set; }
    }

Sample usage:

        var game = new GameManager
        {
            player = new Player { gameObject = new GameObject { Name = "Stu"} }
        };

        // somewhere else...
        object source;
        var name = GetProperty("player.gameObject.Name", game, out source);
        var value = name.GetValue(source); // returns "Stu"

My question is: This only works for properties obviously, how can I make it work for both properties and fields? - The thing is, MemberInfo is common between FieldInfo and PropertyInfo but it doesn't have a GetValue so I can't return a MemberInfo. I've been reading about expressions, but not sure how they'd help me here...

Again, what I'm looking for is (given a source) the ability to parse the following: X.Y.Z where X, Y, Z could be a property or a field.

EDIT:

So I modified the code a bit to do what I wanted - but it's not what you call a squeaky clean code, too many out params:

    public static bool TryParse(string path, object start, out PropertyInfo pinfo, out FieldInfo finfo, out object source)
    {
        if (string.IsNullOrEmpty(path))
            throw new ArgumentException();

        var type = start.GetType();
        var paths = path.Split('.');

        source = start;
        pinfo = null;
        finfo = null;

        for (int i = 0; i < paths.Length; i++) {
            var subpath = paths[i];
            pinfo = type.GetProperty(subpath);
            if (pinfo == null) {
                finfo = type.GetField(subpath);
                if (finfo == null)
                    return false;
            }
            if (i < paths.Length - 1) {
                source = pinfo == null ? finfo.GetValue(source) : pinfo.GetValue(source);
                type = source.GetType();
            }
        }
        return true;
    }

Usage:

        var game = new GameManager
        {
            player = new Player { gameObject = new GameObject { Name = "Stu" } }
        };

        object source;
        PropertyInfo pinfo;
        FieldInfo finfo;
        if (TryParse("player.gameObject.Name", game, out pinfo, out finfo, out source)) {
            var value = pinfo == null ? finfo.GetValue(source) : pinfo.GetValue(source);
        }

It does the parsing I want, but there must be something better...


Solution

  • I think that you can try to use my free open source library Dynamic Expresso.

    You can write something like:

    var game = new GameManager
        {
            player = new Player { gameObject = new GameObject { Name = "Stu" } }
        };
    
    var interpreter = new Interpreter();
    var parameters = new[] {
                new Parameter("game", game)
                };
    var result = interpreter.Eval("game.player.gameObject.Name", parameters);
    

    You can also extract the parsed Expression to get information about the properties/fields and it also support more complex operations (indexer, functions, ...). Parsed expressions are compiled and can be invoked one or more times.