С#: Получение имен свойств в цепочке из выражения лямбда

Я разрабатываю API, который использует лямбда-выражения для указания свойств. Я использую этот знаменитый фрагмент кода, подобный этому (это упрощено и неполно, просто чтобы понять, о чем я говорю):

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

Называется так:

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

Теперь я хотел бы указать путь свойства путем цепочки имен свойств, например:

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

Foo должен иметь возможность разбить путь на свои имена свойств ("Name" и "Length"). Есть ли способ сделать это с разумными усилиями?


Существует как-то похожий вопрос, но я думаю, что они пытаются комбинировать лямбда-выражения там.

Другой вопрос также относится к именам вложенных свойств, но я не совсем понимаю, о чем они говорят.

Ответ 1

Что-то вроде этого?

public void Foo<T, P>(Expression<Func<T, P>> expr)
{
    MemberExpression me;
    switch (expr.Body.NodeType)
    {
        case ExpressionType.Convert:
        case ExpressionType.ConvertChecked:
            var ue = expr.Body as UnaryExpression;
            me = ((ue != null) ? ue.Operand : null) as MemberExpression;
            break;
        default:
            me = expr.Body as MemberExpression;
            break;
    }

    while (me != null)
    {
        string propertyName = me.Member.Name;
        Type propertyType = me.Type;

        Console.WriteLine(propertyName + ": " + propertyType);

        me = me.Expression as MemberExpression;
    }
}

Ответ 2

Я немного поиграл с ExpressionVisitor:

public static class PropertyPath<TSource>
{
    public static IReadOnlyList<MemberInfo> Get<TResult>(Expression<Func<TSource, TResult>> expression)
    {
        var visitor = new PropertyVisitor();
        visitor.Visit(expression.Body);
        visitor.Path.Reverse();
        return visitor.Path;
    }

    private class PropertyVisitor : ExpressionVisitor
    {
        internal readonly List<MemberInfo> Path = new List<MemberInfo>();

        protected override Expression VisitMember(MemberExpression node)
        {
            if (!(node.Member is PropertyInfo))
            {
                throw new ArgumentException("The path can only contain properties", nameof(node));
            }

            this.Path.Add(node.Member);
            return base.VisitMember(node);
        }
    }
}

Использование:

var path = string.Join(".", PropertyPath<string>.Get(x => x.Length).Select(p => p.Name));

Ответ 3

Старый вопрос, я знаю... но если это только имена, которые вам нужны, еще более простой способ сделать это:

expr.ToString().Split('.').Skip(1) 

EDIT:

public class A
{
    public B Property { get; set; }
}

public class B
{
    public C field;
}

[Fact]
public void FactMethodName()
{
    var exp = (Expression<Func<A, object>>) (x => x.Property.field);
    foreach (var part in exp.ToString().Split('.').Skip(1))
        Console.WriteLine(part);

    // Output:
    // Property
    // field
}

Ответ 4

У меня есть общий DTO стандарта .NET между клиентом и сервером, и выражения являются отличным способом создания строк запросов, которые могут быть перестроены и выполнены на стороне API.

Идеальный способ создания безопасных запросов типа по сети.

Я отвлекся, мне нужен также путь к собственности

x => x.Siblings.Age 

Чтобы создать строку, как

"Siblings.Age"

Я пошел с этим

    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);
    }

Ответ 5

    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);
        }
    }