Search code examples
c#.netpropertieslinq-expressions

Get Full Class Property "Tree" Name as String


What I am trying to do is probably a little strange. But I am trying to (this is the best I can explain it) use reflection to get a full class property tree name as a string.


Successful example so far:

By using expressions I am able to convert:

() => Model.Cargo.Id

into the string:

"Model.Cargo.Id"

My problem now is when I am using an array in the mix, I do not get the array name. All I get is the last properties name.

Unsuccessful example:

Model.CargoTasks[j].IsSet

Only returns me the string:

"IsSet"

Ideally I want the following string result:

"Model.CargoTasks[0].IsSet"

I am probably asking a little much to get the index included in the result, but it would be fantasic if this were possible.


The code I am using to process these examples is as follows:

public static string ToMemberAccess<TResult>(this Expression<Func<TResult>> expression)
{
    // Get the body of the expression
    Expression body = expression.Body;
    if (body.NodeType != ExpressionType.MemberAccess && body.NodeType != ExpressionType.Convert)
    {
        throw new ArgumentException("Property expression must be of the form '() => SomeProperty'", "expression");
    }

    var memberExpression = expression.Body as MemberExpression ?? ((UnaryExpression)expression.Body).Operand as MemberExpression;
    var stuff = GetMemberNames(memberExpression);
    stuff.Reverse();

    return string.Join(".", stuff);
}

static List<string> GetMemberNames(MemberExpression expression,  List<string> actual = null)
{
    if (actual == null) actual = new List<string>();

    var member = expression.Member;
    var subExp = expression.Expression as MemberExpression;
    actual.Add(member.Name);

    if(subExp != null) actual = GetMemberNames(subExp, actual);

    return actual;
}

Thanks in advance! Any help will be greatly appreciated!


Solution

  • To get the value in the indexer, you must compile and execute the expression - which is prohibitively expensive, but it can be done using a modified version of ExpressionStringBuilder. Note that I've added a parameter, compileConstants. When it's set to false, the output will be something like Model.CargoTasks[_.j].IsSet.

    Note that this sample visitor is incomplete (i.e. it doesn't support all kinds of expressions). You can complement it using the code in GitHub.

    public static string ToMemberAccess<TResult>(Expression<Func<TResult>> expression, bool compileConstants = false)
    {
        var builder = new ExpressionStringBuilder(compileConstants);
        builder.Visit(expression);
        return builder.ToString();
    }
    
    internal class ExpressionStringBuilder : ExpressionVisitor
    {
        private readonly bool _compileConstants;
        private readonly StringBuilder _out;
    
        public ExpressionStringBuilder(bool compileConstants)
        {
            _compileConstants = compileConstants;
            _out = new StringBuilder();
        }
    
        protected override Expression VisitConstant(ConstantExpression node)
        {
            if (node.Value != null)
            {
                string text = node.Value.ToString();
                if (node.Value is string)
                {
                    Out("\"");
                    Out(text);
                    Out("\"");
                }
                else if (text == node.Value.GetType().ToString())
                {
                    Out('_');
                }
                else
                {
                    Out(text);
                }
            }
            else
            {
                Out("null");
            }
            return node;
        }
    
        protected override Expression VisitMethodCall(MethodCallExpression node)
        {
            int num = 0;
            Expression expression = node.Object;
            if (Attribute.GetCustomAttribute(node.Method, typeof(ExtensionAttribute)) != null)
            {
                num = 1;
                expression = node.Arguments[0];
            }
            var name = node.Method.Name;
            var isIndexer = name == "get_Item";
            if (expression != null)
            {
                Visit(expression);
                if (!isIndexer)
                {
                    Out('.');
                }
            }
            if (isIndexer)
                Out('[');
            else
            {
                Out(name);
                Out('(');
            }
            int i = num;
            int count = node.Arguments.Count;
            while (i < count)
            {
                if (i > num)
                {
                    Out(", ");
                }
                VisitArgument(node.Arguments[i]);
                i++;
            }
            Out(isIndexer ? ']' : ')');
            return node;
        }
    
        protected override Expression VisitIndex(IndexExpression node)
        {
            if (node.Object != null)
            {
                Visit(node.Object);
            }
            else
            {
                Out(node.Indexer.DeclaringType.Name);
            }
            if (node.Indexer != null)
            {
                Out(".");
                Out(node.Indexer.Name);
            }
    
            Out('[');
            for (var index = 0; index < node.Arguments.Count; index++)
            {
                if (index > 0)
                {
                    Out(", ");
                }
                var expression = node.Arguments[index];
                VisitArgument(expression);
            }
            Out(']');
            return node;
        }
    
        protected override Expression VisitLambda<T>(Expression<T> node)
        {
            Visit(node.Body);
            return node;
        }
    
        protected override Expression VisitMember(MemberExpression node)
        {
            OutMember(node.Expression, node.Member);
            return node;
        }
    
        public override string ToString()
        {
            return _out.ToString();
        }
    
        private void VisitArgument(Expression expression)
        {
            if (_compileConstants)
            {
                // TODO: possibly check the expression is not dependent on parameters
                var value = Expression.Lambda(expression).Compile().DynamicInvoke();
                Out(value + string.Empty);
    
            }
            else
            {
                VisitArgument(expression);
            }
        }
    
        private void OutMember(Expression instance, MemberInfo member)
        {
            if (instance != null)
            {
                Visit(instance);
                if (_out.Length > 0)
                    Out('.');
                Out(member.Name);
                return;
            }
            Out(member.DeclaringType.Name + "." + member.Name);
        }
    
        private void Out(char c)
        {
            _out.Append(c);
        }
    
        private void Out(string s)
        {
            _out.Append(s);
        }
    }