Retrieving Property name from lambda expression
也有一个更好的方式得到的属性名称的时候,时代不同了,在通过λexpression吗? 这里是我目前的一切。 P / < >
EG。 P / < >
1 | GetSortingInfo<User>(u => u.UserId); |
它的工作,由铸造它作为memberexpression只有当属性是一个字符串。因为不是所有的性质是字符串的我要使用对象但然后它会返回的unaryexpression为那些。 P / < >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | 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; } |
我最近做了一个非常类似的事情,使类型安全的onPropertyChanged方法。
下面是一个将返回表达式的propertyinfo对象的方法。如果表达式不是属性,则引发异常。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | 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; } |
使用
1 | var propertyInfo = GetPropertyInfo(someUserObject, u => u.UserID); |
我找到了另一种方法,让源和属性强类型化,并显式地推断lambda的输入。不确定这是否是正确的术语,但这里是结果。
1 2 3 4 5 6 7 | 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); } |
然后像这样称呼它。
1 | GetInfo((User u) => u.UserId); |
瞧,它起作用了。谢谢大家。
我也在玩同样的东西,然后把这个搞清楚。它没有经过全面测试,但似乎可以处理值类型的问题(遇到的unaryExpression问题)
1 2 3 4 5 6 7 8 9 10 11 | 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; } |
1 2 3 4 | public string GetName<TSource, TField>(Expression<Func<TSource, TField>> Field) { return (Field.Body as MemberExpression ?? ((UnaryExpression)Field.Body).Operand as MemberExpression).Member.Name; } |
它处理成员和一元表达式。不同的是,如果表达式表示值类型,您将得到一个
为了便于阅读(@jowen),这里有一个扩展的等价物:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | 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; } |
当涉及到
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | 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); } |
现在示例用法:
1 2 |
如果
现在在C 6中,您可以简单地使用这样的名称
它有很多好处,其中之一就是它是在编译时完成的,而不是运行时。
https://msdn.microsoft.com/en-us/magazine/dn802602.aspx
这是获取字段/属性/索引器/方法/扩展方法/结构的委托/类/接口/委托/数组的字符串名称的一般实现。我已经测试了静态/实例和非通用/通用变体的组合。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | //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); } |
这个东西也可以用一个简单的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | //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"); } } } |
我喜欢递归方法,尽管第二种方法可能更容易阅读。人们可以这样称呼它:
1 2 3 4 5 6 7 8 9 | 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(); |
打印最后一个成员。
注:
如果是像
这不适用于
C 7模式匹配:
1 2 3 4 5 6 7 8 9 10 11 12 | 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()); } } |
例子:
1 2 3 4 5 6 | public static RouteValueDictionary GetInfo<T>(this HtmlHelper html, Expression<Func<T, object>> action) where T : class { var name = action.GetMemberName(); return GetInfo(html, name); } |
这是卡梅伦提出的方法的更新。第一个参数不是必需的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | 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; } |
您可以执行以下操作:
1 2 | var propertyInfo = GetPropertyInfo<SomeType>(u => u.UserID); var propertyInfo = GetPropertyInfo((SomeType u) => u.UserID); |
扩展方法:
1 2 3 4 5 6 7 8 9 10 11 12 | 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; } |
你可以:
1 2 3 | SomeType someInstance = null; string propName = someInstance.NameOfProperty(i => i.Length); PropertyInfo propInfo = someInstance.GetPropertyInfo(i => i.Length); |
我发现,一些建议的答案深入到
例)
如果您尝试使用EntityFramework
我发现,仅仅解析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | /// <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 |
(检查分隔符甚至可能是杀伤力过大)
演示(LIQPAD)演示+比较代码——https://gist.github.com/zaus/6992590
我正在使用一种扩展方法来处理pre-c_6项目,而那些针对c_6的项目则使用name of()。
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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; } } |
我称之为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | 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); |
它既适用于字段,也适用于属性。
嗯,没必要打电话给
(重新评论)有关灵活排序的更多信息,请参阅此处。
这是另一个答案:
1 2 3 4 5 6 7 | 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; } |
我已经完成了与下面的方法类似的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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); } } } |
更复杂的基类如下所示。它处理从lambda表达式到属性名的转换。注意,属性实际上是伪属性,因为只使用名称。但它对视图模型和视图模型上的属性的引用似乎是透明的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 | 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类的)属性标记为已修改,因为默认方法只接受字符串。这是我从酒店取名字的方法:
1 2 3 4 5 6 7 | 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); } |
如果要获取多个字段,我将离开此函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | /// <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); } |
下面是另一种基于此答案获取propertyinfo的方法。它消除了对对象实例的需要。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | /// <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; } |
可以这样称呼:
1 | var propertyInfo = GetPropertyInfo((User u) => u.UserID); |
我已经更新了@cameron的答案,包括一些针对
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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; } |
从.NET 4.0开始,可以使用
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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); } } |
以下是您如何使用此访问者:
1 2 3 4 5 6 7 | 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."); } |