Получение имени свойства из лямбда-выражения
Есть ли лучший способ получить имя свойства при передаче через лямбда-выражение? Вот что у меня сейчас есть.
например.
GetSortingInfo<User>(u => u.UserId);
Он работал, выражая его как выражение выражения только тогда, когда свойство было строкой. потому что не все свойства являются строками, я должен был использовать объект, но тогда он вернул бы унарное выражение для них.
public static RouteValueDictionary GetInfo<T>(this HtmlHelper html,
Expression<Func<T, object>> action) where T : class
{
var expression = GetMemberInfo(action);
string name = expression.Member.Name;
return GetInfo(html, name);
}
private static MemberExpression GetMemberInfo(Expression method)
{
LambdaExpression lambda = method as LambdaExpression;
if (lambda == null)
throw new ArgumentNullException("method");
MemberExpression memberExpr = null;
if (lambda.Body.NodeType == ExpressionType.Convert)
{
memberExpr =
((UnaryExpression)lambda.Body).Operand as MemberExpression;
}
else if (lambda.Body.NodeType == ExpressionType.MemberAccess)
{
memberExpr = lambda.Body as MemberExpression;
}
if (memberExpr == null)
throw new ArgumentException("method");
return memberExpr;
}
24 ответа
Я нашел другой способ, которым вы могли бы сделать, это строго указать тип источника и свойства и явно сделать вывод для лямбда-выражения. Не уверен, что это правильная терминология, но вот результат.
public static RouteValueDictionary GetInfo<T,P>(this HtmlHelper html, Expression<Func<T, P>> action) where T : class
{
var expression = (MemberExpression)action.Body;
string name = expression.Member.Name;
return GetInfo(html, name);
}
А потом назови это так.
GetInfo((User u) => u.UserId);
и вуаля это работает.
Спасибо всем.
Недавно я сделал очень похожую вещь, чтобы сделать безопасный от типа метод OnPropertyChanged.
Вот метод, который возвращает объект PropertyInfo для выражения. Выдает исключение, если выражение не является свойством.
public PropertyInfo GetPropertyInfo<TSource, TProperty>(
TSource source,
Expression<Func<TSource, TProperty>> propertyLambda)
{
Type type = typeof(TSource);
MemberExpression member = propertyLambda.Body as MemberExpression;
if (member == null)
throw new ArgumentException(string.Format(
"Expression '{0}' refers to a method, not a property.",
propertyLambda.ToString()));
PropertyInfo propInfo = member.Member as PropertyInfo;
if (propInfo == null)
throw new ArgumentException(string.Format(
"Expression '{0}' refers to a field, not a property.",
propertyLambda.ToString()));
if (type != propInfo.ReflectedType &&
!type.IsSubclassOf(propInfo.ReflectedType))
throw new ArgumentException(string.Format(
"Expression '{0}' refers to a property that is not from type {1}.",
propertyLambda.ToString(),
type));
return propInfo;
}
source
Параметр используется, чтобы компилятор мог делать вывод типа при вызове метода. Вы можете сделать следующее
var propertyInfo = GetPropertyInfo(someUserObject, u => u.UserID);
Я играл с тем же самым и работал над этим. Он не полностью протестирован, но, похоже, решает проблему с типами значений (проблема с одним выражением, с которой вы столкнулись)
public static string GetName(Expression<Func<object>> exp)
{
MemberExpression body = exp.Body as MemberExpression;
if (body == null) {
UnaryExpression ubody = (UnaryExpression)exp.Body;
body = ubody.Operand as MemberExpression;
}
return body.Member.Name;
}
public string GetName<TSource, TField>(Expression<Func<TSource, TField>> Field)
{
return (Field.Body as MemberExpression ?? ((UnaryExpression)Field.Body).Operand as MemberExpression).Member.Name;
}
Это обрабатывает член и унарные выражения. Разница в том, что вы получите UnaryExpression
если ваше выражение представляет тип значения, тогда как вы получите MemberExpression
если ваше выражение представляет ссылочный тип. Все может быть приведено к объекту, но типы значений должны быть упакованы. Вот почему существует UnaryExpression. Ссылка.
Ради удобства чтения (@Jowen), вот расширенный эквивалент:
public string GetName<TSource, TField>(Expression<Func<TSource, TField>> Field)
{
if (object.Equals(Field, null))
{
throw new NullReferenceException("Field is required");
}
MemberExpression expr = null;
if (Field.Body is MemberExpression)
{
expr = (MemberExpression)Field.Body;
}
else if (Field.Body is UnaryExpression)
{
expr = (MemberExpression)((UnaryExpression)Field.Body).Operand;
}
else
{
const string Format = "Expression '{0}' not supported.";
string message = string.Format(Format, Field);
throw new ArgumentException(message, "Field");
}
return expr.Member.Name;
}
С C# 7 сопоставления с шаблоном:
public static string GetMemberName<T>(this Expression<T> expression)
{
switch (expression.Body)
{
case MemberExpression m:
return m.Member.Name;
case UnaryExpression u when u.Operand is MemberExpression m:
return m.Member.Name;
default:
throw new NotImplementedException(expression.GetType().ToString());
}
}
Пример:
public static RouteValueDictionary GetInfo<T>(this HtmlHelper html,
Expression<Func<T, object>> action) where T : class
{
var name = action.GetMemberName();
return GetInfo(html, name);
}
Теперь в C# 6 вы можете просто использовать nameof, как это nameof(User.UserId)
что имеет много преимуществ, среди которых то, что это делается во время компиляции, а не во время выполнения.
Это общая реализация для получения строкового имени полей / свойств / индексаторов / методов / методов расширения / делегатов структуры / класса / интерфейса / делегата / массива. Я проверил с комбинациями статических / экземпляра и неуниверсальных / универсальных вариантов.
//involves recursion
public static string GetMemberName(this LambdaExpression memberSelector)
{
Func<Expression, string> nameSelector = null; //recursive func
nameSelector = e => //or move the entire thing to a separate recursive method
{
switch (e.NodeType)
{
case ExpressionType.Parameter:
return ((ParameterExpression)e).Name;
case ExpressionType.MemberAccess:
return ((MemberExpression)e).Member.Name;
case ExpressionType.Call:
return ((MethodCallExpression)e).Method.Name;
case ExpressionType.Convert:
case ExpressionType.ConvertChecked:
return nameSelector(((UnaryExpression)e).Operand);
case ExpressionType.Invoke:
return nameSelector(((InvocationExpression)e).Expression);
case ExpressionType.ArrayLength:
return "Length";
default:
throw new Exception("not a proper member selector");
}
};
return nameSelector(memberSelector.Body);
}
Эта вещь может быть написана в простой while
цикл тоже:
//iteration based
public static string GetMemberName(this LambdaExpression memberSelector)
{
var currentExpression = memberSelector.Body;
while (true)
{
switch (currentExpression.NodeType)
{
case ExpressionType.Parameter:
return ((ParameterExpression)currentExpression).Name;
case ExpressionType.MemberAccess:
return ((MemberExpression)currentExpression).Member.Name;
case ExpressionType.Call:
return ((MethodCallExpression)currentExpression).Method.Name;
case ExpressionType.Convert:
case ExpressionType.ConvertChecked:
currentExpression = ((UnaryExpression)currentExpression).Operand;
break;
case ExpressionType.Invoke:
currentExpression = ((InvocationExpression)currentExpression).Expression;
break;
case ExpressionType.ArrayLength:
return "Length";
default:
throw new Exception("not a proper member selector");
}
}
}
Мне нравится рекурсивный подход, хотя второй может быть проще для чтения. Это можно назвать так:
someExpr = x => x.Property.ExtensionMethod()[0]; //or
someExpr = x => Static.Method().Field; //or
someExpr = x => VoidMethod(); //or
someExpr = () => localVariable; //or
someExpr = x => x; //or
someExpr = x => (Type)x; //or
someExpr = () => Array[0].Delegate(null); //etc
string name = someExpr.GetMemberName();
распечатать последний член.
Замечания:
В случае цепочечных выражений типа
A.B.C
, "C" возвращается.Это не работает с
const
s, индексаторы массива илиenum
s (невозможно охватить все случаи).
Есть крайний случай, когда дело доходит до Array
.Length. Хотя "Длина" выставляется как свойство, его нельзя использовать ни в одном из ранее предложенных решений.
using Contract = System.Diagnostics.Contracts.Contract;
using Exprs = System.Linq.Expressions;
static string PropertyNameFromMemberExpr(Exprs.MemberExpression expr)
{
return expr.Member.Name;
}
static string PropertyNameFromUnaryExpr(Exprs.UnaryExpression expr)
{
if (expr.NodeType == Exprs.ExpressionType.ArrayLength)
return "Length";
var mem_expr = expr.Operand as Exprs.MemberExpression;
return PropertyNameFromMemberExpr(mem_expr);
}
static string PropertyNameFromLambdaExpr(Exprs.LambdaExpression expr)
{
if (expr.Body is Exprs.MemberExpression) return PropertyNameFromMemberExpr(expr.Body as Exprs.MemberExpression);
else if (expr.Body is Exprs.UnaryExpression) return PropertyNameFromUnaryExpr(expr.Body as Exprs.UnaryExpression);
throw new NotSupportedException();
}
public static string PropertyNameFromExpr<TProp>(Exprs.Expression<Func<TProp>> expr)
{
Contract.Requires<ArgumentNullException>(expr != null);
Contract.Requires<ArgumentException>(expr.Body is Exprs.MemberExpression || expr.Body is Exprs.UnaryExpression);
return PropertyNameFromLambdaExpr(expr);
}
public static string PropertyNameFromExpr<T, TProp>(Exprs.Expression<Func<T, TProp>> expr)
{
Contract.Requires<ArgumentNullException>(expr != null);
Contract.Requires<ArgumentException>(expr.Body is Exprs.MemberExpression || expr.Body is Exprs.UnaryExpression);
return PropertyNameFromLambdaExpr(expr);
}
Теперь пример использования:
int[] someArray = new int[1];
Console.WriteLine(PropertyNameFromExpr( () => someArray.Length ));
Если PropertyNameFromUnaryExpr
не проверял ArrayLength
, "someArray" будет выводиться на консоль (кажется, что компилятор генерирует прямой доступ к полю Backing Length, как оптимизация, даже в Debug, таким образом, в особом случае).
Я обнаружил, что некоторые из предложенных ответов, которые углубляются в MemberExpression
/UnaryExpression
не захватывать вложенные / вложенные свойства.
ех) o => o.Thing1.Thing2
возвращается Thing1
скорее, чем Thing1.Thing2
,
Это различие важно, если вы пытаетесь работать с EntityFramework DbSet.Include(...)
,
Я обнаружил, что просто разбор Expression.ToString()
похоже работает нормально и сравнительно быстро. Я сравнил это с UnaryExpression
версия, и даже получение ToString
от Member/UnaryExpression
чтобы увидеть, если это было быстрее, но разница была незначительной. Пожалуйста, поправьте меня, если это ужасная идея.
Метод продления
/// <summary>
/// Given an expression, extract the listed property name; similar to reflection but with familiar LINQ+lambdas. Technique @via https://stackru.com/a/16647343/1037948
/// </summary>
/// <remarks>Cheats and uses the tostring output -- Should consult performance differences</remarks>
/// <typeparam name="TModel">the model type to extract property names</typeparam>
/// <typeparam name="TValue">the value type of the expected property</typeparam>
/// <param name="propertySelector">expression that just selects a model property to be turned into a string</param>
/// <param name="delimiter">Expression toString delimiter to split from lambda param</param>
/// <param name="endTrim">Sometimes the Expression toString contains a method call, something like "Convert(x)", so we need to strip the closing part from the end</param>
/// <returns>indicated property name</returns>
public static string GetPropertyName<TModel, TValue>(this Expression<Func<TModel, TValue>> propertySelector, char delimiter = '.', char endTrim = ')') {
var asString = propertySelector.ToString(); // gives you: "o => o.Whatever"
var firstDelim = asString.IndexOf(delimiter); // make sure there is a beginning property indicator; the "." in "o.Whatever" -- this may not be necessary?
return firstDelim < 0
? asString
: asString.Substring(firstDelim+1).TrimEnd(endTrim);
}//-- fn GetPropertyNameExtended
(Проверка на разделитель может быть даже излишней)
Демо (LinqPad)
Демонстрация + код сравнения - https://gist.github.com/zaus/6992590
Вот обновление метода, предложенного Кэмероном. Первый параметр не обязателен.
public PropertyInfo GetPropertyInfo<TSource, TProperty>(
Expression<Func<TSource, TProperty>> propertyLambda)
{
Type type = typeof(TSource);
MemberExpression member = propertyLambda.Body as MemberExpression;
if (member == null)
throw new ArgumentException(string.Format(
"Expression '{0}' refers to a method, not a property.",
propertyLambda.ToString()));
PropertyInfo propInfo = member.Member as PropertyInfo;
if (propInfo == null)
throw new ArgumentException(string.Format(
"Expression '{0}' refers to a field, not a property.",
propertyLambda.ToString()));
if (type != propInfo.ReflectedType &&
!type.IsSubclassOf(propInfo.ReflectedType))
throw new ArgumentException(string.Format(
"Expresion '{0}' refers to a property that is not from type {1}.",
propertyLambda.ToString(),
type));
return propInfo;
}
Вы можете сделать следующее:
var propertyInfo = GetPropertyInfo<SomeType>(u => u.UserID);
var propertyInfo = GetPropertyInfo((SomeType u) => u.UserID);
Методы расширения:
public static PropertyInfo GetPropertyInfo<TSource, TProperty>(this TSource source,
Expression<Func<TSource, TProperty>> propertyLambda) where TSource : class
{
return GetPropertyInfo(propertyLambda);
}
public static string NameOfProperty<TSource, TProperty>(this TSource source,
Expression<Func<TSource, TProperty>> propertyLambda) where TSource : class
{
PropertyInfo prodInfo = GetPropertyInfo(propertyLambda);
return prodInfo.Name;
}
Вы можете:
SomeType someInstance = null;
string propName = someInstance.NameOfProperty(i => i.Length);
PropertyInfo propInfo = someInstance.GetPropertyInfo(i => i.Length);
Это может быть оптимальным
public static string GetPropertyName<TResult>(Expression<Func<TResult>> expr)
{
var memberAccess = expr.Body as MemberExpression;
var propertyInfo = memberAccess?.Member as PropertyInfo;
var propertyName = propertyInfo?.Name;
return propertyName;
}
Я использую метод расширения для проектов до C# 6 и nameof() для тех, кто ориентирован на C# 6.
public static class MiscExtentions
{
public static string NameOf<TModel, TProperty>(this object @object, Expression<Func<TModel, TProperty>> propertyExpression)
{
var expression = propertyExpression.Body as MemberExpression;
if (expression == null)
{
throw new ArgumentException("Expression is not a property.");
}
return expression.Member.Name;
}
}
И я называю это так:
public class MyClass
{
public int Property1 { get; set; }
public string Property2 { get; set; }
public int[] Property3 { get; set; }
public Subclass Property4 { get; set; }
public Subclass[] Property5 { get; set; }
}
public class Subclass
{
public int PropertyA { get; set; }
public string PropertyB { get; set; }
}
// result is Property1
this.NameOf((MyClass o) => o.Property1);
// result is Property2
this.NameOf((MyClass o) => o.Property2);
// result is Property3
this.NameOf((MyClass o) => o.Property3);
// result is Property4
this.NameOf((MyClass o) => o.Property4);
// result is PropertyB
this.NameOf((MyClass o) => o.Property4.PropertyB);
// result is Property5
this.NameOf((MyClass o) => o.Property5);
Он отлично работает как с полями, так и со свойствами.
Ну не надо звонить .Name.ToString()
, но в целом это все, да. Единственное, что вам может понадобиться, x.Foo.Bar
должен возвращать "Foo", "Bar" или исключение - т.е. нужно ли вам вообще повторять.
(re comment) Подробнее о гибкой сортировке смотрите здесь.
Я оставляю эту функцию, если вы хотите получить несколько полей:
/// <summary>
/// Get properties separated by , (Ex: to invoke 'd => new { d.FirstName, d.LastName }')
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="exp"></param>
/// <returns></returns>
public static string GetFields<T>(Expression<Func<T, object>> exp)
{
MemberExpression body = exp.Body as MemberExpression;
var fields = new List<string>();
if (body == null)
{
NewExpression ubody = exp.Body as NewExpression;
if (ubody != null)
foreach (var arg in ubody.Arguments)
{
fields.Add((arg as MemberExpression).Member.Name);
}
}
return string.Join(",", fields);
}
Это еще один ответ:
public static string GetPropertyName<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression)
{
var metaData = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
return metaData.PropertyName;
}
Я сделал INotifyPropertyChanged
Реализация аналогична методу ниже. Здесь свойства хранятся в словаре в базовом классе, показанном ниже. Конечно, не всегда желательно использовать наследование, но для моделей представлений я думаю, что это приемлемо и дает очень чистые ссылки на свойства в классах моделей представлений.
public class PhotoDetailsViewModel
: PropertyChangedNotifierBase<PhotoDetailsViewModel>
{
public bool IsLoading
{
get { return GetValue(x => x.IsLoading); }
set { SetPropertyValue(x => x.IsLoading, value); }
}
public string PendingOperation
{
get { return GetValue(x => x.PendingOperation); }
set { SetPropertyValue(x => x.PendingOperation, value); }
}
public PhotoViewModel Photo
{
get { return GetValue(x => x.Photo); }
set { SetPropertyValue(x => x.Photo, value); }
}
}
Несколько более сложный базовый класс показан ниже. Он обрабатывает перевод от лямбда-выражения до имени свойства. Обратите внимание, что свойства действительно псевдо-свойства, так как используются только имена. Но он будет прозрачным для модели представления и ссылок на свойства модели представления.
public class PropertyChangedNotifierBase<T> : INotifyPropertyChanged
{
readonly Dictionary<string, object> _properties = new Dictionary<string, object>();
protected U GetValue<U>(Expression<Func<T, U>> property)
{
var propertyName = GetPropertyName(property);
return GetValue<U>(propertyName);
}
private U GetValue<U>(string propertyName)
{
object value;
if (!_properties.TryGetValue(propertyName, out value))
{
return default(U);
}
return (U)value;
}
protected void SetPropertyValue<U>(Expression<Func<T, U>> property, U value)
{
var propertyName = GetPropertyName(property);
var oldValue = GetValue<U>(propertyName);
if (Object.ReferenceEquals(oldValue, value))
{
return;
}
_properties[propertyName] = value;
RaisePropertyChangedEvent(propertyName);
}
protected void RaisePropertyChangedEvent<U>(Expression<Func<T, U>> property)
{
var name = GetPropertyName(property);
RaisePropertyChangedEvent(name);
}
protected void RaisePropertyChangedEvent(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private static string GetPropertyName<U>(Expression<Func<T, U>> property)
{
if (property == null)
{
throw new NullReferenceException("property");
}
var lambda = property as LambdaExpression;
var memberAssignment = (MemberExpression) lambda.Body;
return memberAssignment.Member.Name;
}
public event PropertyChangedEventHandler PropertyChanged;
}
Я создал метод расширения для ObjectStateEntry, чтобы иметь возможность помечать свойства (классов POCO Entity Framework) как измененные безопасным способом, поскольку метод по умолчанию принимает только строку. Вот мой способ получить имя от собственности:
public static void SetModifiedProperty<T>(this System.Data.Objects.ObjectStateEntry state, Expression<Func<T>> action)
{
var body = (MemberExpression)action.Body;
string propertyName = body.Member.Name;
state.SetModifiedProperty(propertyName);
}
Вот еще один способ получить PropertyInfo на основе этого ответа. Это устраняет необходимость в экземпляре объекта.
/// <summary>
/// Get metadata of property referenced by expression. Type constrained.
/// </summary>
public static PropertyInfo GetPropertyInfo<TSource, TProperty>(Expression<Func<TSource, TProperty>> propertyLambda)
{
return GetPropertyInfo((LambdaExpression) propertyLambda);
}
/// <summary>
/// Get metadata of property referenced by expression.
/// </summary>
public static PropertyInfo GetPropertyInfo(LambdaExpression propertyLambda)
{
// https://stackru.com/questions/671968/retrieving-property-name-from-lambda-expression
MemberExpression member = propertyLambda.Body as MemberExpression;
if (member == null)
throw new ArgumentException(string.Format(
"Expression '{0}' refers to a method, not a property.",
propertyLambda.ToString()));
PropertyInfo propInfo = member.Member as PropertyInfo;
if (propInfo == null)
throw new ArgumentException(string.Format(
"Expression '{0}' refers to a field, not a property.",
propertyLambda.ToString()));
if(propertyLambda.Parameters.Count() == 0)
throw new ArgumentException(String.Format(
"Expression '{0}' does not have any parameters. A property expression needs to have at least 1 parameter.",
propertyLambda.ToString()));
var type = propertyLambda.Parameters[0].Type;
if (type != propInfo.ReflectedType &&
!type.IsSubclassOf(propInfo.ReflectedType))
throw new ArgumentException(String.Format(
"Expression '{0}' refers to a property that is not from type {1}.",
propertyLambda.ToString(),
type));
return propInfo;
}
Это можно назвать так:
var propertyInfo = GetPropertyInfo((User u) => u.UserID);
GetPropetyAccess() доступен, если вы можете сослаться на efcore.
using Microsoft.EntityFrameworkCore.Infrastructure;
var propertyInfo = lambda.GetPropetyAccess(); //PropertyInfo
var propertyName = propertyInfo.Name;
Я обновил ответ @Cameron, чтобы включить некоторые проверки безопасности Convert
набранные лямбда-выражения:
PropertyInfo GetPropertyName<TSource, TProperty>(
Expression<Func<TSource, TProperty>> propertyLambda)
{
var body = propertyLambda.Body;
if (!(body is MemberExpression member)
&& !(body is UnaryExpression unary
&& (member = unary.Operand as MemberExpression) != null))
throw new ArgumentException($"Expression '{propertyLambda}' " +
"does not refer to a property.");
if (!(member.Member is PropertyInfo propInfo))
throw new ArgumentException($"Expression '{propertyLambda}' " +
"refers to a field, not a property.");
var type = typeof(TSource);
if (!propInfo.DeclaringType.GetTypeInfo().IsAssignableFrom(type.GetTypeInfo()))
throw new ArgumentException($"Expresion '{propertyLambda}' " +
"refers to a property that is not from type '{type}'.");
return propInfo;
}
static void Main(string[] args)
{
var prop = GetPropertyInfo<MyDto>(_ => _.MyProperty);
MyDto dto = new MyDto();
dto.MyProperty = 666;
var value = prop.GetValue(dto);
// value == 666
}
class MyDto
{
public int MyProperty { get; set; }
}
public static PropertyInfo GetPropertyInfo<TSource>(Expression<Func<TSource, object>> propertyLambda)
{
Type type = typeof(TSource);
var member = propertyLambda.Body as MemberExpression;
if (member == null)
{
var unary = propertyLambda.Body as UnaryExpression;
if (unary != null)
{
member = unary.Operand as MemberExpression;
}
}
if (member == null)
{
throw new ArgumentException(string.Format("Expression '{0}' refers to a method, not a property.",
propertyLambda.ToString()));
}
var propInfo = member.Member as PropertyInfo;
if (propInfo == null)
{
throw new ArgumentException(string.Format("Expression '{0}' refers to a field, not a property.",
propertyLambda.ToString()));
}
if (type != propInfo.ReflectedType && !type.IsSubclassOf(propInfo.ReflectedType))
{
throw new ArgumentException(string.Format("Expression '{0}' refers to a property that is not from type {1}.",
propertyLambda.ToString(), type));
}
return propInfo;
}
Начиная с.NET 4.0 вы можете использовать ExpressionVisitor
найти недвижимость:
class ExprVisitor : ExpressionVisitor {
public bool IsFound { get; private set; }
public string MemberName { get; private set; }
public Type MemberType { get; private set; }
protected override Expression VisitMember(MemberExpression node) {
if (!IsFound && node.Member.MemberType == MemberTypes.Property) {
IsFound = true;
MemberName = node.Member.Name;
MemberType = node.Type;
}
return base.VisitMember(node);
}
}
Вот как вы используете этого посетителя:
var visitor = new ExprVisitor();
visitor.Visit(expr);
if (visitor.IsFound) {
Console.WriteLine("First property in the expression tree: Name={0}, Type={1}", visitor.MemberName, visitor.MemberType.FullName);
} else {
Console.WriteLine("No properties found.");
}
Предполагая (TModel как класс)
Expression<Func<TModel, TValue>> expression
получить имя свойства с помощью
expression.GetPropertyInfo().Name;
Функция расширения:
public static PropertyInfo GetPropertyInfo<TType, TReturn>(this Expression<Func<TType, TReturn>> property)
{
LambdaExpression lambda = property;
var memberExpression = lambda.Body is UnaryExpression expression
? (MemberExpression)expression.Operand
: (MemberExpression)lambda.Body;
return (PropertyInfo)memberExpression.Member;
}
Я делаю следующее:
Вот метод, проверенный на свойства, которые были POCOS, Списки, строки, DateTimes и примитивы:
public string GetFieldName<T>(Expression<Func<T, object>> excludedField){
if (excludedField.Body.NodeType == ExpressionType.Convert) //Value type
{
UnaryExpression body = excludedField.Body as UnaryExpression;
MemberExpression operand = body?.Operand as MemberExpression;
return operand?.Member.Name;
}
else if(excludedField.Body.NodeType == ExpressionType.MemberAccess) //Ref type
{
MemberExpression body = excludedField.Body as MemberExpression;
return body?.Member.Name;
}
}