經過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
(不可能涵蓋全部狀況)。