Вот хитрость: любое выражение этой формы ...
obj => obj.A.B.C // etc.
... на самом деле это просто набор вложенных MemberExpression
объектов.
Первое, что вы получили:
MemberExpression: obj.A.B.C
Expression: obj.A.B // MemberExpression
Member: C
Оценка Expression
выше как MemberExpression
дает вам:
MemberExpression: obj.A.B
Expression: obj.A // MemberExpression
Member: B
Наконец, выше , что (на «вершине») у вас есть:
MemberExpression: obj.A
Expression: obj // note: not a MemberExpression
Member: A
Таким образом, кажется очевидным, что способ решения этой проблемы заключается в проверке свойства Expression
для MemberExpression
вплоть до точки, в которой он больше не является MemberExpression
.
ОБНОВЛЕНИЕ : Кажется, в вашей проблеме добавлено вращение. Возможно, у вас есть лямбда, которая выглядит как Func<T, int>
...
p => p.Age
... но на самом деле a Func<T, object>
; в этом случае компилятор преобразует вышеприведенное выражение в:
p => Convert(p.Age)
Приспособление к этой проблеме на самом деле не так сложно, как может показаться. Взгляните на мой обновленный код, чтобы разобраться с ним. Обратите внимание, что путем абстрагирования кода для передачи MemberExpression
в его собственный метод (TryFindMemberExpression
) этот подход делает метод GetFullPropertyName
достаточно чистым и позволяет добавлять дополнительные проверки в будущем - если, возможно, вы Вы окажетесь перед новым сценарием, который вы изначально не учитывали - без необходимости разбираться со слишком большим количеством кода.
Для иллюстрации: этот код работал для меня.
// code adjusted to prevent horizontal overflow
static string GetFullPropertyName<T, TProperty>
(Expression<Func<T, TProperty>> exp)
{
MemberExpression memberExp;
if (!TryFindMemberExpression(exp.Body, out memberExp))
return string.Empty;
var memberNames = new Stack<string>();
do
{
memberNames.Push(memberExp.Member.Name);
}
while (TryFindMemberExpression(memberExp.Expression, out memberExp));
return string.Join(".", memberNames.ToArray());
}
// code adjusted to prevent horizontal overflow
private static bool TryFindMemberExpression
(Expression exp, out MemberExpression memberExp)
{
memberExp = exp as MemberExpression;
if (memberExp != null)
{
// heyo! that was easy enough
return true;
}
// if the compiler created an automatic conversion,
// it'll look something like...
// obj => Convert(obj.Property) [e.g., int -> object]
// OR:
// obj => ConvertChecked(obj.Property) [e.g., int -> long]
// ...which are the cases checked in IsConversion
if (IsConversion(exp) && exp is UnaryExpression)
{
memberExp = ((UnaryExpression)exp).Operand as MemberExpression;
if (memberExp != null)
{
return true;
}
}
return false;
}
private static bool IsConversion(Expression exp)
{
return (
exp.NodeType == ExpressionType.Convert ||
exp.NodeType == ExpressionType.ConvertChecked
);
}
Использование:
Expression<Func<Person, string>> simpleExp = p => p.FirstName;
Expression<Func<Person, string>> complexExp = p => p.Address.State.Abbreviation;
Expression<Func<Person, object>> ageExp = p => p.Age;
Console.WriteLine(GetFullPropertyName(simpleExp));
Console.WriteLine(GetFullPropertyName(complexExp));
Console.WriteLine(GetFullPropertyName(ageExp));
Выход:
FirstName
Address.State.Abbreviation
Age