当通过lambda表达式传入时,是否有更好的方法来获得属性名?
这是我目前拥有的。
eg.
GetSortingInfo<User>(u => u.UserId);
它只在属性为字符串时才将其转换为成员表达式。因为不是所有的属性都是字符串,我必须使用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;
}
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;
}
我发现了另一种方法,就是让源和属性具有强类型,并显式地推断lambda的输入。不确定这是否是正确的术语,但这是结果。
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);
瞧,它起作用了。
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;
}
使用c# 7模式匹配:
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());
}
}
例子:
public static RouteValueDictionary GetInfo<T>(this HtmlHelper html,
Expression<Func<T, object>> action) where T : class
{
var name = action.GetMemberName();
return GetInfo(html, name);
}
[更新]c# 8模式匹配:
public static string GetMemberName<T>(this Expression<T> expression) => expression.Body switch
{
MemberExpression m => m.Member.Name,
UnaryExpression u when u.Operand is MemberExpression m => m.Member.Name,
_ => throw new NotImplementedException(expression.GetType().ToString())
};