Magpie
Magpie

Reputation: 7173

Retrieving nested PropertyInfo via expressions

I'm trying to create a function where I can pass in an expression to say which properties I'm interested in. I have it working for top level properties but not for nested properties.

Example model

public class Foo {
    public string Name { get; set; } 
    public List<Foo> List { get; set; }
}

What I have so far

    private PropertyInfo GetPropertyInfo<TModel>(Expression<Func<TModel, object>> selector)
    {
        if (selector.NodeType != ExpressionType.Lambda)
        {
            throw new ArgumentException("Selector must be lambda expression", nameof(selector));
        }

        var lambda = (LambdaExpression)selector;

        var memberExpression = ExtractMemberExpression(lambda.Body);
        if (memberExpression == null)
            throw new ArgumentException("Selector must be member access expression", nameof(selector));

        if (memberExpression.Member.DeclaringType == null)
        {
            throw new InvalidOperationException("Property does not have declaring type");
        }

        return memberExpression.Member.DeclaringType.GetProperty(memberExpression.Member.Name);
    }

    private static MemberExpression ExtractMemberExpression(Expression expression)
    {
        if (expression.NodeType == ExpressionType.MemberAccess)
        {
            return ((MemberExpression)expression);
        }

        if (expression.NodeType == ExpressionType.Convert)
        {
            var operand = ((UnaryExpression)expression).Operand;
            return ExtractMemberExpression(operand);
        }

        return null;
    }

So:

GetPropertyInfo<Foo>(x => x.Name); // works
GetPropertyInfo<Foo>(x => x.List.Select(y => y.Name); <-- how do I get this?

I'm looking for a way to pick any property from a complex object.

Upvotes: 1

Views: 644

Answers (1)

Evk
Evk

Reputation: 101483

You need to extend your ExtractMemberExpression just a bit to accept Select call expression:

private MemberExpression ExtractMemberExpression(Expression expression) {
    if (expression.NodeType == ExpressionType.MemberAccess) {
        return ((MemberExpression) expression);
    }

    if (expression.NodeType == ExpressionType.Convert) {
        var operand = ((UnaryExpression) expression).Operand;
        return ExtractMemberExpression(operand);
    }
    if (expression.NodeType == ExpressionType.Lambda) {            
        return ExtractMemberExpression(((LambdaExpression) expression).Body);
    }

    if (expression.NodeType == ExpressionType.Call) {
        var call = (MethodCallExpression) expression;
        // any method named Select with 2 parameters will do
        if (call.Method.Name == "Select" && call.Arguments.Count == 2) {
            return ExtractMemberExpression(call.Arguments[1]);
        }
    }

    return null;
}

Upvotes: 2

Related Questions