2

I'm trying to write an extension-method to retrieve a property-path from a given LambdaExpression.

I want to be able to call it like

var path = ((Event e) => e.Address.City.Name).ToChainedPath();

which would return Address.City.Name as string separated by ..

Currently my extension looks like this

public static string ToChainedPath<TSource, TProperty>(this Expression<Func<TSource, TProperty>> expr, char separator = '.')
{
    MemberExpression me;

    switch (expr.Body.NodeType)
    {
        case ExpressionType.Convert:
        case ExpressionType.ConvertChecked:
            var ue = expr.Body as UnaryExpression;
            me = ue?.Operand as MemberExpression;
            break;
        default:
            me = expr.Body as MemberExpression;
            break;
    }

    var propertyNames = new List<string>();

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

    propertyNames.Reverse();

    return string.Join(separator, propertyNames);
}

Which is working fine when used like

Expression<Func<Event, string>> expression = e => e.Address.City.Name;
var propertyChain = expression.ToChainedPath();

But this does not work for what I want because (Event e) => e.Address.City.Name is Func<Event, string> and not Expression<Func<Event,string>>. I already tried to convert the Func to an Expression but nothing helped so far.

I also changed the extension to extend Func instead of Expression but then I loose the Body.

Is this even possible to do?

KingKerosin
  • 3,639
  • 4
  • 38
  • 77
  • Your exact requirement is not possible. A few similar syntaxes are possible: `((Expression>)(e => e.Address.City.Name)).ToChainedPath()` or `GetChainedPath(e => e.Address.City.Name)`. – Sweeper May 26 '21 at 10:26
  • @Sweeper I was afraid that this is not possible. So I will go with the second approach and leaving the extension behind. Thanks for clarification. If you post an answer I can mark it as solution – KingKerosin May 26 '21 at 10:33

1 Answers1

2

A lambda expression itself does not have a type (See the spec), so the compiler won't know where to find ToChainedPath if you just do:

((Event e) => e.Address.City.Name).ToChainedPath();

You haven't given it any type information about what the lambda expression should be converted to (a delegate type? an expression type?). Therefore, this exact syntax is not possible.

One other way that you can give type information, is with a method parameter.

EnclosingClass.GetChainedPath(e => e.Address.City.Name);

If the compiler can resolve what GetChainedPath is, it will know that it has a parameter of type Expression<Func<Event, string>>:

public static string GetChainedPath<TSource, TProperty>(Expression<Func<TSource, TProperty>> expr, char separator = '.')

and so it knows what type the lambda should be converted to.

If you want this to be extra concise (omitting EnclosingClass.), you can do a using static for GetChainedPath.

using static YourNamespace.EnclosingClass;
Sweeper
  • 213,210
  • 22
  • 193
  • 313