通过lambda表达式传递时,是否有更好的方法来获取属性名称? 这是我目前拥有的。
例如。
GetSortingInfo<User>(u => u.UserId);
仅当属性为字符串时,才将其强制转换为memberexpression。 因为并非所有属性都是字符串,所以我不得不使用object,但是它将为这些返回unaryexpression。
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;
}
#1楼
我已经完成了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); }
}
}
稍微复杂一些的基类如下所示。 它处理从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;
}
#2楼
对于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中也是如此,这是一种特殊情况)。
#3楼
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;
}
#4楼
我发现,一些深入到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
(检查定界符甚至可能会过大)
演示(LinqPad)
演示+比较代码-https://gist.github.com/zaus/6992590
#5楼
这是获取字段/属性/索引器/方法/扩展方法/结构/类/接口/委托/数组的代理的字符串名称的常规实现。 我已经测试了静态/实例和非通用/通用变体的组合。
//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
(不可能涵盖所有情况)。
来源:oschina
链接:https://my.oschina.net/u/3797416/blog/3159466