2

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.

Community
  • 1
  • 1
Abhijeet Patel
  • 6,562
  • 8
  • 50
  • 93
  • Have you looked at VS2015 and nameof? – jmoreno Jan 09 '16 at 04:54
  • As I said"nameof" is not an option here – Abhijeet Patel Jan 09 '16 at 05:55
  • Sorry, somehow I missed that. – jmoreno Jan 09 '16 at 15:15
  • 1
    Why do you need to support all these cases? I think handling such a degree of freedom in the specified expression tree will quickly lead to totally invalid expressions being passed in, and not returning the expected result. As such: handle errors explicitly. Whats with the method call expression? I cant imagine a valid use case for this. if the result is fine with a new message, why is the method not static? and then why not just invoke it without expression trees? And for the z => z.Prop in your member expression the Expression is a ParameterExpression, not a ConstantExpression. – MBoros Jan 10 '16 at 10:03
  • You're probably right in that I should probably support UnaryExlressions only.For the MethodCallExpression:Basically I patch the Message with incoming JSON data and tHat API creates the Expression dynamically based on the properties that need an update. Given the complexity of the above,It might be prudent to simply accept an array of property names for this case and use the strong typed UnaryExpression for cases when the property names are well known ahead of time.Thoughts? – Abhijeet Patel Jan 10 '16 at 20:22

1 Answers1

2

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);
        }
    }
}
Sagi
  • 8,972
  • 3
  • 33
  • 41