Abhijeet Patel
Abhijeet Patel

Reputation: 6878

Retrieving strongly typed Property names using Expression Trees

I'm looking to use Expression trees to get property names of a Domain type. This will ultimately be used in an EF context to send Updates for dirty fields. I have the following so far and wanted to confirm that the below is a more a less a complete implementation and whether there is a more flexible/performant way to do the same:

class Message
{
    public int ID
    {
        get;
        set;
    }
    public string Body
    {
        get;
        set;
    }
}

 internal static class Program
 {
    public static string GetPropertyName(Expression<Func<Message,object>> exp)
    {
        string propName = "";

        UnaryExpression ue = exp.Body as UnaryExpression;
        if(ue != null)
        {
            propName = (ue.Operand as MemberExpression).Member.Name;
        }
        MemberExpression me = exp.Body as MemberExpression;
        if(me != null)
        {
            var constExpression = me.Expression as ConstantExpression;
            var field = me.Member as FieldInfo;
            if (constExpression != null) 
                if (field != null) 
                    propName = field.GetValue(constExpression.Value).ToString();

        }
        ConstantExpression ce = exp.Body as ConstantExpression;
        if(ce != null)
        {
            propName =  ce.Value.ToString();
        }
        MethodCallExpression mc = exp.Body as MethodCallExpression;
        if(mc != null)
        {
            //ParameterExpression param = Expression.Parameter(typeof(Message));
            ParameterExpression param = mc.Arguments.OfType<ParameterExpression>().FirstOrDefault();
            propName = Expression.Lambda(exp.Body, param).Compile().DynamicInvoke(new Message()).ToString();
        }
        return propName;
    }
    private static string SomeMethod(Message m)
    {
        return "ID";
    }     
    private static Expression<Func<Message,object>>[] GetExpressions()
    {
        string[] props = new[] { "ID", "Name" };
        var expressions  = 
            props.Select(p =>  
                {
                    Expression<Func<Message,object>> exp = m => p;
                    return exp;
                }).ToArray();
        return expressions;
    }
    public static void Main(string[] args)
    {
        string str = "id";
        Expression<Func<Message, object>> exp1 = m => str;
        Expression<Func<Message, object>> exp2 = m => "id";
        Expression<Func<Message, object>> exp3 = m => m.ID;
        Expression<Func<Message, object>> exp4 = m => SomeMethod(m);
        var expressions = GetExpressions();


        string s = GetPropertyName(exp1);
        s = GetPropertyName(exp2);
        s = GetPropertyName(exp3);
        s = GetPropertyName(exp4);

        foreach (var exp in expressions)
        {
            s = GetPropertyName(exp);
        }
    }
}

This SO Post attempts to do something similar but does not seem to cover the use cases above. NOTE: Using "nameof" is not an option here.

Upvotes: 2

Views: 299

Answers (1)

Sagi
Sagi

Reputation: 9294

I would use an ExpressionVisitor to do it.

class Message
{
    public int ID {
        get;
        set;
    }
    public string Body {
        get;
        set;
    }
}

internal static class Program
{
    private static string SomeMethod(Message m) {
        return "ID";
    }
    private static Expression<Func<Message, object>>[] GetExpressions() {
        string[] props = new[] { "ID", "Name" };
        var expressions =
            props.Select(p => {
                Expression<Func<Message, object>> exp = m => p;
                return exp;
            }).ToArray();
        return expressions;
    }

    public class NameResolverExpressionVisitor : ExpressionVisitor
    {
        private Expression<Func<Message, object>> exp;

        public string Name { get; private set; }

        public override Expression Visit(Expression node) {
            var casted = node as Expression<Func<Message, object>>;

            if (casted != null) {
                exp = casted;
            }

            return base.Visit(node);
        }

        protected override Expression VisitMember(MemberExpression node) {
            var constExpression = node.Expression as ConstantExpression;
            var field = node.Member as FieldInfo;

            if (constExpression != null && field != null) {
                Name = field.GetValue(constExpression.Value).ToString();
            }

            return base.VisitMember(node);
        }
        protected override Expression VisitMethodCall(MethodCallExpression node) {
            Name = exp.Compile().DynamicInvoke(new Message()).ToString();

            return base.VisitMethodCall(node);
        }

        protected override Expression VisitUnary(UnaryExpression node) {
            var memberExpression = node.Operand as MemberExpression;

            if (memberExpression != null) {
                Name = memberExpression.Member.Name;
            }

            return base.VisitUnary(node);
        }

        protected override Expression VisitConstant(ConstantExpression node) {
            if (node.Value.GetType().Equals(typeof(string))) {
                Name = node.Value.ToString();
            }

            return base.VisitConstant(node);
        }
    }

    public static void Main(string[] args) {
        string str = "id";
        Expression<Func<Message, object>> exp1 = m => str;
        Expression<Func<Message, object>> exp2 = m => "id";
        Expression<Func<Message, object>> exp3 = m => m.ID;
        Expression<Func<Message, object>> exp4 = m => SomeMethod(m);
        var expressions = GetExpressions();


        var visitor = new NameResolverExpressionVisitor();

        visitor.Visit(exp1);
        Console.WriteLine(visitor.Name);
        visitor.Visit(exp2);
        Console.WriteLine(visitor.Name);
        visitor.Visit(exp3);
        Console.WriteLine(visitor.Name);
        visitor.Visit(exp4);
        Console.WriteLine(visitor.Name);

        foreach (var exp in expressions) {
            visitor.Visit(exp);
            Console.WriteLine(visitor.Name);
        }
    }
}

Upvotes: 2

Related Questions