С#: Получение имен свойств в цепочке из выражения лямбда
Я разрабатываю 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);
}
}