C#:从 lambda 表达式获取链中的属性名称

发布于 2024-08-09 07:53:58 字数 884 浏览 6 评论 0原文

我正在开发一个使用 lambda 表达式来指定属性的 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")。有没有办法通过合理的努力来做到这一点?


有一个看起来类似的问题,但我认为他们正在尝试结合那里有 lambda 表达式。

另一个问题也正在处理嵌套属性名称,但我不太明白他们在说什么。

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

伊面 2024-08-16 07:53:58

像这样的东西吗?

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

Something like this?

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;
    }
}
黯淡〆 2024-08-16 07:53:58

我玩了一下 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));

I played a little with 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);
        }
    }
}

Usage:

var path = string.Join(".", PropertyPath<string>.Get(x => x.Length).Select(p => p.Name));
愁杀 2024-08-16 07:53:58

老问题,我知道......但如果这只是您需要的名称,则更简单的方法是:

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

编辑:

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
}

Old question, I know... but if it's only the names you need, an even simpler way to do it is:

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
}
拥醉 2024-08-16 07:53:58

我在客户端和服务器之间有一个共享的 .NET 标准 DTO,表达式是创建可以在 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);
    }

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);
    }
梦回旧景 2024-08-16 07:53:58
    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);
        }
    }
    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);
        }
    }
风启觞 2024-08-16 07:53:58

我重构了这些答案以使用递归,因此不需要反转顺序。我只需要属性树,因此省略了除 MemberExpressions 之外的所有内容。如果需要的话,添加功能应该很简单。

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

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);
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文