通过lambda表达式传入时,是否有更好的方法来获取属性名称?这是我现在拥有的.
例如.
GetSortingInfo(u => u.UserId);
只有当属性是字符串时,它才能将其作为元素表达式进行处理.因为不是所有属性都是字符串我必须使用对象,但它会返回一个单一表达式.
public static RouteValueDictionary GetInfo(this HtmlHelper html, Expression > 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; }
Cameron MacF.. 333
我最近做了一个非常类似的事情来制作一个类型安全的OnPropertyChanged方法.
这是一个方法,它将返回表达式的PropertyInfo对象.如果表达式不是属性,则抛出异常.
public PropertyInfo GetPropertyInfo( TSource source, Expression > 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; }
使用该source
参数,以便编译器可以对方法调用进行类型推断.您可以执行以下操作
var propertyInfo = GetPropertyInfo(someUserObject, u => u.UserID);
此外,从2012年开始,类型推断在没有source参数的情况下工作正常. (16认同)
反思(没有双关语)是的,你是对的. (11认同)
@GrayKing不会和`if(!propInfo.ReflectedType.IsAssignableFrom(type))`一样吗? (8认同)
为什么在那里有关于TSource的最后一次检查?lambda是强类型的,所以我不认为这是必要的. (6认同)
最后一个if语句应该是:`if(type!= propInfo.ReflectedType &&!type.IsSubclassOf(propInfo.ReflectedType)&&!propInfo.ReflectedType.IsAssignableFrom(type))`以允许接口. (5认同)
@HappyNomad想象一个对象,它有一个成员,一个第三种类型的实例.`u => u.OtherType.OtherTypesProperty`会创建最后一个语句正在检查的情况. (4认同)
ToString()在异常消息格式化中是多余的 (4认同)
我们可以更改签名以将类的类型作为第一个参数吗?然后我们可能不需要创建'someUserObject'而是发送typeof(userclass).那可能吗? (2认同)
Schotime.. 186
我发现你可以做的另一种方法是强烈输入源和属性,并明确推断lambda的输入.不确定这是否是正确的术语,但这是结果.
public static RouteValueDictionary GetInfo(this HtmlHelper html, Expression > action) where T : class { var expression = (MemberExpression)action.Body; string name = expression.Member.Name; return GetInfo(html, name); }
然后这样称呼它.
GetInfo((User u) => u.UserId);
并且它有效.
谢谢大家.
我最近做了一个非常类似的事情来制作一个类型安全的OnPropertyChanged方法.
这是一个方法,它将返回表达式的PropertyInfo对象.如果表达式不是属性,则抛出异常.
public PropertyInfo GetPropertyInfo( TSource source, Expression > 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; }
使用该source
参数,以便编译器可以对方法调用进行类型推断.您可以执行以下操作
var propertyInfo = GetPropertyInfo(someUserObject, u => u.UserID);
我发现你可以做的另一种方法是强烈输入源和属性,并明确推断lambda的输入.不确定这是否是正确的术语,但这是结果.
public static RouteValueDictionary GetInfo(this HtmlHelper html, Expression > action) where T : class { var expression = (MemberExpression)action.Body; string name = expression.Member.Name; return GetInfo(html, name); }
然后这样称呼它.
GetInfo((User u) => u.UserId);
并且它有效.
谢谢大家.
我正在玩同样的事情,并努力工作.它没有完全测试,但似乎处理值类型的问题(你遇到的unaryexpression问题)
public static string GetName(Expression> exp) { MemberExpression body = exp.Body as MemberExpression; if (body == null) { UnaryExpression ubody = (UnaryExpression)exp.Body; body = ubody.Operand as MemberExpression; } return body.Member.Name; }
public string GetName(Expression > Field) { return (Field.Body as MemberExpression ?? ((UnaryExpression)Field.Body).Operand as MemberExpression).Member.Name; }
这会处理成员和一元表达式.不同之处在于,UnaryExpression
如果表达式表示值类型,则将得到a,而MemberExpression
如果表达式表示引用类型,则将得到a .可以将所有内容强制转换为对象,但必须将值类型装箱.这就是UnaryExpression存在的原因.参考.
对于可读性(@Jowen),这是一个扩展的等价物:
public string GetName(Expression > 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(this Expression 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(this HtmlHelper html, Expression > action) where T : class { var name = action.GetMemberName(); return GetInfo(html, name); }
现在在C#6,你可以简单地使用nameof这样nameof(User.UserId)
这有很多好处,其中包括在编译时完成,而不是运行时.
https://msdn.microsoft.com/en-us/magazine/dn802602.aspx
对于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(Exprs.Expression > expr) { Contract.Requires (expr != null); Contract.Requires (expr.Body is Exprs.MemberExpression || expr.Body is Exprs.UnaryExpression); return PropertyNameFromLambdaExpr(expr); } public static string PropertyNameFromExpr (Exprs.Expression > expr) { Contract.Requires (expr != null); Contract.Requires (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"将被打印到控制台(编译器似乎生成直接访问支持Length 字段,作为优化,甚至在Debug中,因此特殊情况).
这是获取struct/class/interface/delegate/array的fields/properties/indexers/methods/extension methods/delegates的字符串名称的一般实现.我已经使用静态/实例和非泛型/通用变体的组合进行了测试.
//involves recursion public static string GetMemberName(this LambdaExpression memberSelector) { FuncnameSelector = 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();
打印最后一个成员.
注意:
在链式表达式的情况下,A.B.C
返回"C".
这不适用于const
s,数组索引器或enum
s(不可能涵盖所有情况).
这是Cameron提出的方法的更新.第一个参数不是必需的.
public PropertyInfo GetPropertyInfo( Expression > 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; }
您可以执行以下操作:
var propertyInfo = GetPropertyInfo(u => u.UserID); var propertyInfo = GetPropertyInfo((SomeType u) => u.UserID);
扩展方法:
public static PropertyInfo GetPropertyInfo(this TSource source, Expression > propertyLambda) where TSource : class { return GetPropertyInfo(propertyLambda); } public static string NameOfProperty (this TSource source, Expression > propertyLambda) where TSource : class { PropertyInfo prodInfo = GetPropertyInfo(propertyLambda); return prodInfo.Name; }
您可以:
SomeType someInstance = null; string propName = someInstance.NameOfProperty(i => i.Length); PropertyInfo propInfo = someInstance.GetPropertyInfo(i => i.Length);
我发现一些建议的答案深入到MemberExpression
/ UnaryExpression
不捕获嵌套/子属性.
ex)o => o.Thing1.Thing2
返回Thing1
而不是Thing1.Thing2
.
如果您尝试使用EntityFramework,这种区别很重要DbSet.Include(...)
.
我发现只是解析它Expression.ToString()
似乎工作正常,而且相对较快.我将它与UnaryExpression
版本进行了比较,甚至ToString
离开了Member/UnaryExpression
,看看它是否更快,但差别可以忽略不计.如果这是一个可怕的想法,请纠正我.
////// Given an expression, extract the listed property name; similar to reflection but with familiar LINQ+lambdas. Technique @via /sf/ask/17360801/ /// ///Cheats and uses the tostring output -- Should consult performance differences ///the model type to extract property names ///the value type of the expected property /// expression that just selects a model property to be turned into a string /// Expression toString delimiter to split from lambda param /// Sometimes the Expression toString contains a method call, something like "Convert(x)", so we need to strip the closing part from the end ///indicated property name public static string GetPropertyName(this Expression > 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
我对C#6前项目使用扩展方法,对C#6使用名称().
public static class MiscExtentions { public static string NameOf(this object @object, Expression > propertyExpression) { var expression = propertyExpression.Body as MemberExpression; if (expression == null) { throw new ArgumentException("Expression is not a property."); } return expression.Member.Name; } }
我称之为:
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);
它适用于字段和属性.
嗯,没有必要打电话.Name.ToString()
,但广泛的是关于它,是的.您可能需要考虑的唯一因素是是否x.Foo.Bar
应该返回"Foo","Bar"或异常 - 即您是否需要迭代.
(重新评论)有关灵活排序的更多信息,请参阅此处.