C#: Getting Names of properties in a chain from lambda expression

Viewed 12189

I'm developing a API that uses lambda expressions to specify properties. I'm using this famous piece of code similar to this one (this is simplified and incomplete, just to make clear what I'm talking about):

public void Foo<T, P>(Expression<Func<T, P>> action)
{
    var expression = (MemberExpression)action.Body;
    string propertyName = expression.Member.Name;
    // ...
}

To be called like this:

Foo((String x) => x.Length);

Now I would like to specify a property path by chaining property names, like this:

Foo((MyClass x) => x.Name.Length);

Foo should be able to split the path into its property names ("Name" and "Length"). Is there a way to do this with reasonable effort?


There is a somehow similar looking question, but I think they are trying to combine lambda expressions there.

Another question also is dealing with nested property names, but I don't really understand what they are talking about.

6 Answers

I have a shared .NET standard DTO between client and server and expressions are a great way to create query strings that can be rebuilt and executed Api side.

A perfect way to create Type safe queries over the wire.

I digress, I needed a property path also

x => x.Siblings.Age 

To produce a string like

"Siblings.Age"

I went with this

    public static string GetMemberPath(MemberExpression me)
    {
        var parts = new List<string>();

        while (me != null)
        {
            parts.Add(me.Member.Name);
            me = me.Expression as MemberExpression;
        }

        parts.Reverse();
        return string.Join(".", parts);
    }
    public static string GetPath<T, TProperty>(this Expression<Func<T, TProperty>> exp)
    {
        return string.Join(".", GetItemsInPath(exp).Reverse());
    }

    private static IEnumerable<string> GetItemsInPath<T, TProperty>(Expression<Func<T, TProperty>> exp)
    {
        if (exp == null)
        {
            yield break;
        }
        var memberExp = FindMemberExpression(exp.Body);
        while (memberExp != null)
        {
            yield return memberExp.Member.Name;
            memberExp = FindMemberExpression(memberExp.Expression);
        }
    }

I refactored these answers to use recursion, so no need for order reversal. I only need the property tree, so left out all but MemberExpressions. Should be simple to add functionality if need be.

public IEnumerable<string> PropertyPath<TModel, TValue>(
    Expression<Func<TModel, TValue>> expression)
{
    if (expression.Body is MemberExpression memberExpression)
        return PropertyPathRecurse(memberExpression);
        
    return Enumerable.Empty<string>();
}

private IEnumerable<string> PropertyPathRecurse(MemberExpression? expression)
{
    if (expression is null)
        return Enumerable.Empty<string>();
        
    return PropertyPathRecurse(expression.Expression as MemberExpression)
        .Append(expression.Member.Name);
}
Related