Question

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...

Was it helpful?

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.

OTHER TIPS

Here is hot it can be at Expression trees (there is no check for null and check for property existance):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

namespace ExpressionTrees
{
    public static class ExpressionTreeBuilder
    {
        private static readonly IDictionary<string, Delegate> Lambdas = 
            new Dictionary<string, Delegate>();

        public static T GetValue<T, TInst>(this TInst obj, string propPath, T defVal = default(T))
        {
            var key = String.Format("{0};{1}", propPath, "str");//typeof(T).Name);
            Delegate del;
            if (!Lambdas.TryGetValue(key, out del))
            {
                var instance = Expression.Parameter(typeof(TInst), "obj");

                var currentExpression = 
                    propPath
                    .Split('.')
                    .Aggregate((Expression)instance, Expression.PropertyOrField);

                var lexpr = Expression.Lambda<Func<TInst, T>>(currentExpression, instance);

                del = lexpr.Compile();
                Lambdas.Add(key, del);
            }

            var action = (Func<TInst, T>)del;

            return action.Invoke(obj);
        }

    }
}

And example of usage:

var surv = new Survey() { id = 1, title = "adsf" , User = new User() { Name = "UserName 11"}};
var dynamicUserName = surv.GetValue<string, Survey>("User.Name");

But of course, it's better to use well tested and documented third party library. This code snippet is just for example.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top