从lambdaexpression式中检索属性名称
有一个更好的方式来获得通过lambdaexpression式传入时的属性名称? 这是我目前有。
例如。
GetSortingInfo<User>(u => u.UserId);
只有当属性是一个string的时候,它才会将其作为一个元素来expression。 因为不是所有的属性都是string,我不得不使用对象,但它会返回一个一元expression式。
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; }
我最近做了一个类似的事情,使一个types安全的OnPropertyChanged方法。
这是一个返回expression式的PropertyInfo对象的方法。 如果expression式不是属性,则会引发exception。
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( "Expresion '{0}' refers to a property that is not from type {1}.", propertyLambda.ToString(), type)); return propInfo; }
source
参数被使用,所以编译器可以对方法调用进行types推断。 您可以执行以下操作
var propertyInfo = GetPropertyInfo(someUserObject, u => u.UserID);
我发现另一种可以做到的方式是强制types化源和属性,并显式推断lambda的input。 不知道这是否是正确的术语,但这里是结果。
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);
并说它的作品。
谢谢大家。
我正在玩同样的东西,并进行了处理。 它没有完全testing,但似乎处理值types的问题(你遇到的一元expression问题)
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; }
这处理成员和一元expression式。 不同之处在于,如果expression式表示值types,则将获得UnaryExpression
,而如果expression式表示引用types,则将获得MemberExpression
。 所有东西都可以投射到一个物体上,但是价值types必须是盒装的。 这就是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; }
Array.Length有一个边界情况。 虽然“长度”作为属性公开,但您不能将其用于以前提出的任何解决scheme。
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
, ArrayLength
“someArray”打印到控制台(编译器似乎生成直接访问背景长度字段 ,作为优化,即使在debugging中,因此也是特例)。
这是获取struct / class / interface / delegate / array的字段/属性/索引器/方法/扩展方法/委托的string名称的常规实现。 我已经testing了静态/实例和非generics/通用变体的组合。
//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"); } } }
我喜欢recursion的方法,虽然第二个可能更容易阅读。 人们可以这样称呼它:
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();
打印最后一个成员。
注意:
-
在像
ABC
这样的连锁expression式的情况下,返回“C”。 -
这不适用于
const
,数组索引器或enum
(不可能涵盖所有情况)。
这里是Cameron提出的方法的更新。 第一个参数不是必需的。
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);
我发现一些build议的答案 ,深入到MemberExpression
/ UnaryExpression
不捕获嵌套/子属性。
ex) o => o.Thing1.Thing2
返回Thing1
而不是Thing1.Thing2
。
如果您尝试使用EntityFramework DbSet.Include(...)
则此区别非常重要。
我发现只是parsingExpression.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://stackoverflow.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
现在在C#6中,您可以简单地使用nameof(User.UserId)
这有很多好处,其中之一就是在编译时完成,而不是运行时。
那么,没有必要调用.Name.ToString()
,但广泛的说,是的。 你可能需要的唯一的考虑是x.Foo.Bar
应该返回“Foo”,“Bar”还是一个exception – 也就是说,你是否需要迭代。
(重新评论)更多关于灵活的sorting,请看这里 。
我使用C#6之前的扩展方法和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);
它对于字段和属性都可以正常工作。
我在ObjectStateEntry上创build了一个扩展方法,以便以types安全的方式标记属性(entity frameworkPOCO类的属性),因为默认方法只接受一个string。 这是我从物业获取名字的方法:
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); }
我已经完成了与下面的方法类似的INotifyPropertyChanged
实现。 这里的属性存储在下面显示的基类的字典中。 当然,使用inheritance并不总是可取的,但对于视图模型,我认为这是可以接受的,并在视图模型类中提供非常干净的属性引用。
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); } } }
下面显示了更复杂的基类。 它处理从lambdaexpression式到属性名称的翻译。 请注意,这些属性实际上是伪属性,因为只使用名称。 但是它对于视图模型和对视图模型的属性的引用将是透明的。
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; }
这是另一个答案:
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; }
这是另一种获取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://stackoverflow.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);
我更新了@ Cameron的答案 ,包括对Convert
types的lambdaexpression式进行一些安全检查:
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; }
如果你想获得多个字段,我离开这个函数:
/// <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); }
从.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."); }