首页 文章

从lambda表达式中检索属性名称

提问于
浏览
452

通过lambda表达式传入时,是否有更好的方法来获取属性名称?这是我现在拥有的 .

例如 .

GetSortingInfo<User>(u => u.UserId);

只有当属性是字符串时,它才能将其作为元素表达式进行处理 . 因为并非所有属性都是字符串我必须使用对象,但它会为那些返回一个单一表达式 .

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;
}

19 回答

  • 3

    我在ObjectStateEntry上创建了一个扩展方法,以便能够以类型安全的方式标记属性(Entity Framework POCO类)的属性,因为默认方法只接受字符串 . 这是我从酒店获取名称的方式:

    public static void SetModifiedProperty<T>(this System.Data.Objects.ObjectStateEntry state, Expression<Func<T>> action)
    {
        var body = (MemberExpression)action.Body;
        string propertyName = body.Member.Name;
    
        state.SetModifiedProperty(propertyName);
    }
    
  • 47

    我最近做了一个非常类似的事情来制作一个类型安全的OnPropertyChanged方法 .

    这是一个方法,它将返回表达式的PropertyInfo对象 . 如果表达式不是属性,则抛出异常 .

    public PropertyInfo GetPropertyInfo<TSource, TProperty>(
        TSource source,
        Expression<Func<TSource, TProperty>> 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);
    
  • 16

    我正在为C#6前项目使用扩展方法,对于那些以C#6为目标的项目使用nameof() .

    public static class MiscExtentions
    {
        public static string NameOf<TModel, TProperty>(this object @object, Expression<Func<TModel, TProperty>> 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);
    

    它适用于字段和属性 .

  • 15

    我完成了类似于下面方法的 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;
    }
    
  • 13

    现在在C#6你可以像这样使用nameof nameof(User.UserId)

    这有很多好处,其中包括compile time,而不是运行时 .

    https://msdn.microsoft.com/en-us/magazine/dn802602.aspx

  • 1

    这是另一个答案:

    public static string GetPropertyName<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper,
                                                                          Expression<Func<TModel, TProperty>> expression)
        {
            var metaData = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
    
            return metaData.PropertyName;
        }
    
  • 1
    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存在的原因 . Reference.

    对于可读性(@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

    对于 Array .Length来说,有一个优势 . 虽然'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"将被打印到控制台(编译器似乎生成直接访问支持Length字段,作为优化,即使在Debug中,因此特殊情况) .

  • 19

    这是另一种方式得到PropertyInfo基于this answer.它消除了对象实例的需要 .

    /// <summary>
    /// Get metadata of property referenced by expression. Type constrained.
    /// </summary>
    public static PropertyInfo GetPropertyInfo<TSource, TProperty>(Expression<Func<TSource, TProperty>> propertyLambda)
    {
        return GetPropertyInfo((LambdaExpression) propertyLambda);
    }
    
    /// <summary>
    /// Get metadata of property referenced by expression.
    /// </summary>
    public static PropertyInfo GetPropertyInfo(LambdaExpression propertyLambda)
    {
        // https://stackoverflow.com/questions/671968/retrieving-property-name-from-lambda-expression
        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(propertyLambda.Parameters.Count() == 0)
            throw new ArgumentException(String.Format(
                "Expression '{0}' does not have any parameters. A property expression needs to have at least 1 parameter.",
                propertyLambda.ToString()));
    
        var type = propertyLambda.Parameters[0].Type;
        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;
    }
    

    它可以像这样调用:

    var propertyInfo = GetPropertyInfo((User u) => u.UserID);
    
  • 5

    我发现一些深入到 MemberExpression / UnaryExpressionsuggested answers不会捕获嵌套/子属性 .

    ex) o => o.Thing1.Thing2 返回 Thing1 而不是 Thing1.Thing2 .

    如果您尝试使用EntityFramework DbSet.Include(...) ,这种区别很重要 .

    我发现只是解析 Expression.ToString() 似乎工作正常,而且相对较快 . 我将它与 UnaryExpression 版本进行了比较,甚至将 ToStringMember/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

  • 2

    我正在玩同样的事情,并努力工作 . 它没有经过全面测试,但似乎处理了值类型的问题(你遇到的unaryexpression问题)

    public static string GetName(Expression<Func<object>> exp)
    {
        MemberExpression body = exp.Body as MemberExpression;
    
        if (body == null) {
           UnaryExpression ubody = (UnaryExpression)exp.Body;
           body = ubody.Operand as MemberExpression;
        }
    
        return body.Member.Name;
    }
    
  • 167

    我更新了@Cameron's answer以对 Convert 类型的lambda表达式进行一些安全检查:

    PropertyInfo GetPropertyName<TSource, TProperty>(
    Expression<Func<TSource, TProperty>> propertyLambda)
    {
      var body = propertyLambda.Body;
      if (!(body is MemberExpression member)
        && !(body is UnaryExpression unary
          && (member = unary.Operand as MemberExpression) != null))
        throw new ArgumentException($"Expression '{propertyLambda}' " +
          "does not refer to a property.");
    
      if (!(member.Member is PropertyInfo propInfo))
        throw new ArgumentException($"Expression '{propertyLambda}' " +
          "refers to a field, not a property.");
    
      var type = typeof(TSource);
      if (!propInfo.DeclaringType.GetTypeInfo().IsAssignableFrom(type.GetTypeInfo()))
        throw new ArgumentException($"Expresion '{propertyLambda}' " + 
          "refers to a property that is not from type '{type}'.");
    
      return propInfo;
    }
    
  • 137

    如果你想获得多个字段我会离开这个函数:

    /// <summary>
        /// Get properties separated by , (Ex: to invoke 'd => new { d.FirstName, d.LastName }')
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="exp"></param>
        /// <returns></returns>
        public static string GetFields<T>(Expression<Func<T, object>> exp)
        {
            MemberExpression body = exp.Body as MemberExpression;
            var fields = new List<string>();
            if (body == null)
            {
                NewExpression ubody = exp.Body as NewExpression;
                if (ubody != null)
                    foreach (var arg in ubody.Arguments)
                    {
                        fields.Add((arg as MemberExpression).Member.Name);
                    }
            }
    
            return string.Join(",", fields);
        }
    
  • 1

    从.NET 4.0开始,您可以使用ExpressionVisitor查找属性:

    class ExprVisitor : ExpressionVisitor {
        public bool IsFound { get; private set; }
        public string MemberName { get; private set; }
        public Type MemberType { get; private set; }
        protected override Expression VisitMember(MemberExpression node) {
            if (!IsFound && node.Member.MemberType == MemberTypes.Property) {
                IsFound = true;
                MemberName = node.Member.Name;
                MemberType = node.Type;
            }
            return base.VisitMember(node);
        }
    }
    

    以下是您使用此访问者的方式:

    var visitor = new ExprVisitor();
    visitor.Visit(expr);
    if (visitor.IsFound) {
        Console.WriteLine("First property in the expression tree: Name={0}, Type={1}", visitor.MemberName, visitor.MemberType.FullName);
    } else {
        Console.WriteLine("No properties found.");
    }
    
  • 5

    好吧,没有必要打电话给 .Name.ToString() ,但广泛的是关于它,是的 . 您可能需要的唯一考虑因素是 x.Foo.Bar 是否应返回"Foo","Bar"或异常 - 即您是否需要迭代 .

    (重新评论)有关灵活排序的更多信息,请参阅here .

  • 314

    这是获取struct / class / interface / delegate / array的fields / properties / indexers / methods / extension methods / delegates的字符串名称的一般实现 . 我已经测试了静态/实例和非泛型/通用变体的组合 .

    //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();
    

    打印最后一个成员 .

    注意:

    • 如果链接表达式如 A.B.C ,则返回"C" .

    • 这不适用于 const ,数组索引器或 enum (不可能涵盖所有情况) .

  • 18

    这是method proposed by Cameron的更新 . 第一个参数不是必需的 .

    public PropertyInfo GetPropertyInfo<TSource, TProperty>(
        Expression<Func<TSource, TProperty>> 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<SomeType>(u => u.UserID);
    var propertyInfo = GetPropertyInfo((SomeType u) => u.UserID);
    

    扩展方法:

    public static PropertyInfo GetPropertyInfo<TSource, TProperty>(this TSource source,
        Expression<Func<TSource, TProperty>> propertyLambda) where TSource : class
    {
        return GetPropertyInfo(propertyLambda);
    }
    
    public static string NameOfProperty<TSource, TProperty>(this TSource source,
        Expression<Func<TSource, TProperty>> 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);
    
  • 3

    我发现你可以做的另一种方法是强烈输入源和属性,并明确推断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);
    

    并且它有效 .
    谢谢大家 .

  • 3

    使用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);
    }
    

相关问题