经过lambda表达式传递时,是否有更好的方法来获取属性名称? 这是我目前拥有的。 html
例如。 git
GetSortingInfo<User>(u => u.UserId);
仅当属性为字符串时,才将其强制转换为memberexpression。 由于并不是全部属性都是字符串,因此我不得不使用object,可是它将为这些返回unaryexpression。 github
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; }
我已经完成了INotifyPropertyChanged
实现,相似于下面的方法。 此处的属性存储在下面显示的基类的字典中。 固然并不老是但愿使用继承,可是对于视图模型,我认为这是能够接受的,而且在视图模型类中提供了很是干净的属性引用。 express
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表达式到属性名称的转换。 请注意,这些属性其实是伪属性,由于仅使用名称。 可是它将对视图模型和对视图模型属性的引用透明。 数组
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; }
对于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中也是如此,这是一种特殊状况)。 ui
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的缘由。 参考。 this
为了便于阅读(@Jowen),下面是一个扩展的等效项: spa
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; }
我发现,一些深刻到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://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
(检查定界符甚至可能会过大)
演示+比较代码-https://gist.github.com/zaus/6992590
这是获取字段/属性/索引器/方法/扩展方法/结构/类/接口/委托/数组的代理的字符串名称的常规实现。 我已经测试了静态/实例和非通用/通用变体的组合。
//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();
打印最后一个成员。
注意:
若是使用ABC
之类的连接表达式,则返回“ C”。
这不适用于const
,数组索引器或enum
(不可能涵盖全部状况)。