【问题标题】:Retrieving Property name from lambda expression从 lambda 表达式中检索属性名称
【发布时间】:2010-10-14 21:24:05
【问题描述】:

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

【问题讨论】:

  • 更好的代码?我不这么认为。类型检查仅扩展到整个表达式,因此您确实需要在运行时进行检查。 :(
  • 是的......只是想知道是否有更好的方法来做到这一点,因为它让我觉得有点 hacky。但如果就是这样,那就太酷了。谢谢。
  • 给大家的注意事项:使用此处列出的MemberExpression 方法仅获取成员的name而不是获取实际的@ 987654326@ 本身,因为返回的MemberInfo 在某些“衍生:基本”场景中不能保证是反射类型。见lambda-expression-not-returning-expected-memberinfo。绊了我一次。接受的答案也受此影响。
  • 从 C# 6 开始,您可以简单地使用nameof(),例如:nameof(User.UserId)。不需要辅助方法,它在编译时被替换!

标签: c# linq lambda expression-trees


【解决方案1】:

我最近做了一个非常相似的事情来创建一个类型安全的 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);

【讨论】:

  • 为什么最后一次检查 TSource 在那里? lambda 是强类型的,所以我认为没有必要。
  • 此外,截至 2012 年,类型推断在没有源参数的情况下也能正常工作。
  • @HappyNomad 想象一个对象,它具有作为成员的第三种类型的实例。 u =&gt; u.OtherType.OtherTypesProperty 会创建最后一条语句正在检查的这种情况。
  • 最后一个 if 语句应该是:if (type != propInfo.ReflectedType &amp;&amp; !type.IsSubclassOf(propInfo.ReflectedType) &amp;&amp; !propInfo.ReflectedType.IsAssignableFrom(type)) 也允许接口。
  • @GrayKing 和if(!propInfo.ReflectedType.IsAssignableFrom(type)) 不一样吗?
【解决方案2】:

我发现另一种方法是对源和属性进行强类型化并显式推断 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);

瞧,它的工作原理。

【讨论】:

  • 这个解决方案应该稍微更新一下。请查看以下文章 - 这是link
  • 如果您使用 ASP.Net MVC 并且仅用于 UI 层 (HtmlHelper),这是唯一的选择。
  • 从c# 6.0开始可以使用GetInfo(nameof(u.UserId))
  • 在网络核心我不得不使用这个:var name = ((MemberExpression) ((UnaryExpression) accessor.Body).Operand).Member.Name
  • 此解决方案无法处理 UnaryExpressions。它不能用于doubleint。虽然@Falk 的评论不允许使用string(因为它不处理MemberExpressions)。还有其他解决方案没有这些缺点。
【解决方案3】:

我正在玩同样的事情并解决了这个问题。它没有经过全面测试,但似乎可以处理值类型的问题(您遇到的一元表达式问题)

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

【讨论】:

  • 最近尝试过(来自another question),发现它不处理子属性:o =&gt; o.Thing1.Thing2 将返回Thing2,而不是Thing1.Thing2,如果您尝试使用这是不正确的它在 EntityFramework 中包含
  • AKA (field.Body is UnaryExpression ? ((UnaryExpression)field.Body).Operand : field.Body) as MemberExpression
【解决方案4】:
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;
}

【讨论】:

  • @flem,为了便于阅读,我省略了 ,有什么问题吗? LambdaExpressions.GetName(m => m.Quantity)
  • @soren 我敢肯定,比我更关注的人可能会建议您在传递值类型的表达式时打开代码,避免不必要的装箱/拆箱,但因为表达式是从来没有用这种方法编译和评估过,这可能不是问题。
【解决方案5】:

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

【讨论】:

    【解决方案6】:

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

    这有很多好处,其中之一是这是在compile time 完成的,而不是运行时。

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

    【讨论】:

      【解决方案7】:

      这是获取结构/类/接口/委托/数组的字段/属性/索引器/方法/扩展方法/委托的字符串名称的通用实现。我已经测试了静态/实例和非泛型/泛型变体的组合。

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

      打印最后一个成员。

      注意:

      1. 如果是像A.B.C 这样的链式表达式,则返回“C”。

      2. 这不适用于consts、数组索引器或enums(不可能涵盖所有情况)。

      【讨论】:

        【解决方案8】:

        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”将被打印到控制台(编译器似乎生成对支持长度字段的直接访问,作为一种优化,甚至在调试中,因此是特殊情况)。

        【讨论】:

          【解决方案9】:

          这是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);
          

          【讨论】:

          • 不,他不会将u 推断为某种类型,他不能这样做,因为没有类型可以推断。你可以做的是GetPropertyInfo&lt;SomeType&gt;(u =&gt; u.UserID)
          • 好吧,使用 GetPropertyInfo&lt;SomeType&gt;(u =&gt; u.UserID); 给了我“使用 .GetPropertyInfo(Expression>) 需要 2 个类型参数。”替代GetPropertyInfo((SomeType u) =&gt; u.UserID) 确实有效。可能是什么问题呢? (不使用扩展方法,而是静态的我)。
          【解决方案10】:

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

          例如)o =&gt; 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

          【讨论】:

          • + 1 非常有趣。您是否继续在自己的代码中使用此方法?它工作正常吗?你有没有发现任何边缘情况?
          • 我看不到你的想法。按照您链接的答案o =&gt; o.Thing1.Thing2 不会像您说的那样返回Thing1,而是返回Thing2。事实上,您的答案会返回类似 Thing1.Thing2 的内容,这可能是也可能不是。
          • 不适用于案例 korman 警告:stackoverflow.com/a/11006147/661933。总是最好避免黑客攻击。
          • @nawfal #1 -- 最初的问题是你想要 Thing1.Thing2,而不是Thing1。我说Thing2 的意思是o.Thing1.Thing2,也就是谓词的点。我会更新答案以反映这一意图。
          • @drzaus 对不起,我还是没有收到你。真诚地试图理解。为什么你会说这里的其他答案返回Thing1?我认为它根本不会恢复。
          【解决方案11】:

          我正在为 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);
          

          它适用于字段和属性。

          【讨论】:

            【解决方案12】:

            好吧,没有必要打电话给.Name.ToString(),但总的来说就是这样,是的。您可能需要的唯一考虑是 x.Foo.Bar 是否应该返回“Foo”、“Bar”或异常 - 即您是否需要迭代。

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

            【讨论】:

            • 是的...它只是第一级的东西,用于生成排序列链接。例如。如果我有一个模型并且我想显示列名以进行排序,我可以使用指向该对象的强类型链接来获取动态 linq 不会对此感到担忧的属性名称。干杯。
            • ToString 对于一元表达式应该给出丑陋的结果。
            【解决方案13】:

            如果你想获得多个字段,我离开这个功能:

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

            【讨论】:

            • 你要解释这个吗?
            【解决方案14】:

            这可能是最优的

            public static string GetPropertyName<TResult>(Expression<Func<TResult>> expr)
            {
                var memberAccess = expr.Body as MemberExpression;
                var propertyInfo = memberAccess?.Member as PropertyInfo;
                var propertyName = propertyInfo?.Name;
            
                return propertyName;
            }
            

            【讨论】:

              【解决方案15】:

              我在 ObjectStateEntry 上创建了一个扩展方法,以便能够以类型安全的方式将(实体框架 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);
              }
              

              【讨论】:

                【解决方案16】:

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

                【讨论】:

                • 你基本上是在维护一个财产包。不错,但是来自模型类的 getter 和 setter 的调用要容易一些,比如public bool IsLoading { get { return GetValue(MethodBase.GetCurrentMethod().Name); } set { SetPropertyValue(MethodBase.GetCurrentMethod().Name, value); } }。可能会更慢,但更通用和直接。
                • 实际上实现一个简单的依赖属性系统更难(但不是那么难),但实际上比上述实现要好得多。
                【解决方案17】:

                这是另一个答案:

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

                【讨论】:

                • ModelMetadata 存在于System.Web.Mvc 命名空间中。也许它不适合一般情况
                【解决方案18】:

                这是基于this answer. 获取 PropertyInfo 的另一种方法,它消除了对对象实例的需要。

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

                【讨论】:

                  【解决方案19】:

                  我已更新 @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;
                  }
                  

                  【讨论】:

                    【解决方案20】:

                    从 .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.");
                    }
                    

                    【讨论】:

                      【解决方案21】:
                      static void Main(string[] args)
                      {
                          var prop = GetPropertyInfo<MyDto>(_ => _.MyProperty);
                      
                          MyDto dto = new MyDto();
                          dto.MyProperty = 666;
                      
                          var value = prop.GetValue(dto);
                          // value == 666
                      }
                      
                      class MyDto
                      {
                          public int MyProperty { get; set; }
                      }
                      
                      public static PropertyInfo GetPropertyInfo<TSource>(Expression<Func<TSource, object>> propertyLambda)
                      {
                          Type type = typeof(TSource);
                      
                          var member = propertyLambda.Body as MemberExpression;
                          if (member == null)
                          {
                              var unary = propertyLambda.Body as UnaryExpression;
                              if (unary != null)
                              {
                                  member = unary.Operand as MemberExpression;
                              }
                          }
                          if (member == null)
                          {
                              throw new ArgumentException(string.Format("Expression '{0}' refers to a method, not a property.",
                                  propertyLambda.ToString()));
                          }
                      
                          var 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;
                      }
                      

                      【讨论】:

                        【解决方案22】:

                        GetPropetyAccess()可以参考efcore。

                        using Microsoft.EntityFrameworkCore.Infrastructure;
                        
                        var propertyInfo = lambda.GetPropetyAccess(); //PropertyInfo
                        var propertyName = propertyInfo.Name;
                        

                        【讨论】:

                        • 对于那些使用 .NET Core 2.1 的人,我通过导入 Microsoft.EntityFrameworkCore.Internal 找到了该方法。
                        猜你喜欢
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        • 2021-12-09
                        • 1970-01-01
                        • 1970-01-01
                        • 2021-10-08
                        • 1970-01-01
                        相关资源
                        最近更新 更多