C#: 从lambda表达式中获取属性链的名称
C#: 从lambda表达式中获取属性链的名称
我正在开发一个使用lambda表达式来指定属性的API。我正在使用类似于下面这段著名的代码(这只是简化和不完整的,只是为了清楚我在说什么):
public void Foo(Expression > 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表达式。
另一个问题也涉及嵌套的属性名称,但我真的不明白他们在谈论什么。
C#: 获取lambda表达式中链式调用的属性名称
最近我尝试使用ExpressionVisitor进行了一些实验:
public static class PropertyPath{ public static IReadOnlyList Get (Expression > expression) { var visitor = new PropertyVisitor(); visitor.Visit(expression.Body); visitor.Path.Reverse(); return visitor.Path; } private class PropertyVisitor : ExpressionVisitor { internal readonly List Path = new List (); 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.Get(x => x.Length).Select(p => p.Name));
感谢ExpressionVisitor,这是一个非常简洁的解决方案。个人而言,我会为每个方法调用创建一个PathVisitor实例,而不是使用锁,但文档对此没有任何建议,也没有说明是否创建PathVisitor实例会消耗大量资源。它没有Dispose()方法,这意味着它不会占用太多资源。
我添加了一个改进版本,没有锁定,将泛型参数移到类中,所以你只需要指定TSource,还添加了一个方便的方法,可以返回带有可配置字符串分隔符的字符串: [gist.github.com/anjdreas/862c1cd9983d7525d2ddee0bb2706c3a](https://gist.github.com/anjdreas/862c1cd9983d7525d2ddee0bb2706c3a)
是的,锁定操作是多余的,我已经更新了答案。
能否回忆一下为什么要在路径上调用.Reverse()方法?
我希望路径的顺序与访问者生成的顺序相反:)根据需要进行修改,并记得编写一些测试。
我非常喜欢这个解决方案,因为它非常简洁,但我希望能够禁用方法调用。目前它似乎只是忽略它们,并且ExpressionVisitor不允许重写VisitMethodCall()方法来抛出异常。有人有什么想法吗?
这个解决方案适用于索引属性,比如数组和自定义索引吗?
问题的出现原因是希望从C#的Lambda表达式中获取属性链中的属性名称。在给定的代码示例中,我们可以看到一个名为`Foo`的方法,该方法接受一个`Expression`类型的参数`expr`,该参数是一个Lambda表达式,用于表示一个从类型`T`到类型`P`的函数。该方法的目标是从Lambda表达式中提取属性链中的属性名称。
解决方法是使用`MemberExpression`类来解析Lambda表达式,并迭代属性链,从中提取属性名称和属性类型。该方法首先检查Lambda表达式的`Body.NodeType`属性,以确定表达式类型。如果该属性是`ExpressionType.Convert`或`ExpressionType.ConvertChecked`,则将表达式转换为`UnaryExpression`类型,并从中获取成员表达式。否则,直接从`expr.Body`属性获取成员表达式。然后,使用`me.Member.Name`获取属性名称,并使用`me.Type`获取属性类型。最后,通过将`me.Expression`转换为`MemberExpression`迭代属性链。
另外,如果只需要属性名称,可以使用`expr.ToString().Split('.').Skip(1)`更简单地实现。这将将Lambda表达式转换为字符串,并通过`.`分隔符拆分,然后跳过第一个元素,即方法名称。
如果Lambda链中存在方法调用,则任何前置成员都将被排除在外。
问题的出现原因是用户想要从lambda表达式中获取属性的名称。用户尝试使用ToString方法来实现这个目标,但是发现只能获取到最后一个属性名,而无法获取到整个链上的属性名。
解决方法是使用ToString方法将lambda表达式转换为字符串,然后使用Split方法将字符串拆分成属性名的数组。由于ToString方法返回的字符串以"."分隔属性名,因此可以通过跳过第一个元素来获取到整个链上的属性名。下面是解决方法的代码示例:
expr.ToString().Split('.').Skip(1)
然而,有用户尝试了这个方法后发现并不起作用。原因是这种方法无法处理值类型的装箱情况,并且速度也很慢。因此,建议用户谨慎使用这种方法。
另外,有用户表示对使用ToString方法来获取属性名的方式感到不太舒服。因为ToString方法本来是用于将对象转换为可视化表示的,它并不是一个约定,并且未来的版本可能会改变返回值的格式。尽管如此,这种方法仍然是一种非常简单的实现方式。
用户想要从lambda表达式中获取属性名的问题,可以通过使用ToString方法将lambda表达式转换为字符串,然后使用Split方法将字符串拆分成属性名的数组来解决。用户应该注意该方法在处理值类型的装箱情况下可能会有问题,并且使用ToString方法来获取属性名可能存在一定的风险。