关于c#:从lambda表达式中检索属性名称

Retrieving Property name from lambda expression

也有一个更好的方式得到的属性名称的时候,时代不同了,在通过λexpression吗? 这里是我目前的一切。 P / < >

EG。 P / < >

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

它的工作,由铸造它作为memberexpression只有当属性是一个字符串。因为不是所有的性质是字符串的我要使用对象但然后它会返回的unaryexpression为那些。 P / < >

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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;
}


我最近做了一个非常类似的事情,使类型安全的onPropertyChanged方法。

下面是一个将返回表达式的propertyinfo对象的方法。如果表达式不是属性,则引发异常。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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参数,以便编译器可以对方法调用进行类型推断。您可以执行以下操作

1
var propertyInfo = GetPropertyInfo(someUserObject, u => u.UserID);


我找到了另一种方法,让源和属性强类型化,并显式地推断lambda的输入。不确定这是否是正确的术语,但这里是结果。

1
2
3
4
5
6
7
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);
}

然后像这样称呼它。

1
GetInfo((User u) => u.UserId);

瞧,它起作用了。谢谢大家。


我也在玩同样的东西,然后把这个搞清楚。它没有经过全面测试,但似乎可以处理值类型的问题(遇到的unaryExpression问题)

1
2
3
4
5
6
7
8
9
10
11
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;
}


1
2
3
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。所有内容都可以强制转换为对象,但值类型必须装箱。这就是一元表达式存在的原因。参考文献。

为了便于阅读(@jowen),这里有一个扩展的等价物:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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;
}


当涉及到Array长度时,有一个边缘情况。虽然"length"作为一个属性公开,但您不能在任何先前建议的解决方案中使用它。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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);
}

现在示例用法:

1
2
int[] someArray = new int[1];
Console.WriteLine(PropertyNameFromExpr( () => someArray.Length ));

如果PropertyNameFromUnaryExpr没有检查ArrayLength,则"somearray"将被打印到控制台(编译器似乎生成了对backing-length字段的直接访问,作为一种优化,即使在调试中也是如此,这是一种特殊情况)。


现在在C 6中,您可以简单地使用这样的名称nameof(User.UserId)

它有很多好处,其中之一就是它是在编译时完成的,而不是运行时。

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


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//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循环来写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//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");
        }
    }
}

我喜欢递归方法,尽管第二种方法可能更容易阅读。人们可以这样称呼它:

1
2
3
4
5
6
7
8
9
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(不可能涵盖所有情况)。


  • C 7模式匹配:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    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());
        }
    }

    例子:

    1
    2
    3
    4
    5
    6
    public static RouteValueDictionary GetInfo<T>(this HtmlHelper html,
        Expression<Func<T, object>> action) where T : class
    {
        var name = action.GetMemberName();
        return GetInfo(html, name);
    }


    这是卡梅伦提出的方法的更新。第一个参数不是必需的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    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;
    }

    您可以执行以下操作:

    1
    2
    var propertyInfo = GetPropertyInfo<SomeType>(u => u.UserID);
    var propertyInfo = GetPropertyInfo((SomeType u) => u.UserID);

    扩展方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    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;
    }

    你可以:

    1
    2
    3
    SomeType someInstance = null;
    string propName = someInstance.NameOfProperty(i => i.Length);
    PropertyInfo propInfo = someInstance.GetPropertyInfo(i => i.Length);


    我发现,一些建议的答案深入到MemberExpressionUnaryExpression中,并不能捕获嵌套/子属性。

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

    如果您尝试使用EntityFramework DbSet.Include(...),那么这种区别非常重要。

    我发现,仅仅解析Expression.ToString()似乎工作得很好,而且速度相对较快。我把它与UnaryExpression版本作了比较,甚至让ToStringMember/UnaryExpression版本中去掉,看它是否更快,但差别可以忽略不计。如果这是个糟糕的主意,请纠正我。

    扩展方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    /// <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

    (检查分隔符甚至可能是杀伤力过大)

    演示(LIQPAD)

    演示+比较代码——https://gist.github.com/zaus/6992590


    我正在使用一种扩展方法来处理pre-c_6项目,而那些针对c_6的项目则使用name of()。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    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;
        }
    }

    我称之为:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    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"或异常,即是否需要迭代。

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


    这是另一个答案:

    1
    2
    3
    4
    5
    6
    7
    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;
        }


    我已经完成了与下面的方法类似的INotifyPropertyChanged实现。这里的属性存储在下面所示的基类中的字典中。当然,使用继承并不总是可取的,但是对于视图模型,我认为它是可以接受的,并且在视图模型类中提供了非常干净的属性引用。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    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表达式到属性名的转换。注意,属性实际上是伪属性,因为只使用名称。但它对视图模型和视图模型上的属性的引用似乎是透明的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    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;
    }


    我在ObjectStateEntry上创建了一个扩展方法,以便能够以类型安全的方式将(实体框架POCO类的)属性标记为已修改,因为默认方法只接受字符串。这是我从酒店取名字的方法:

    1
    2
    3
    4
    5
    6
    7
    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);
    }

    如果要获取多个字段,我将离开此函数:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    /// <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);
        }


    下面是另一种基于此答案获取propertyinfo的方法。它消除了对对象实例的需要。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    /// <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;
    }

    可以这样称呼:

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

    我已经更新了@cameron的答案,包括一些针对Convert类型lambda表达式的安全检查:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    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;
    }

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    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);
        }
    }

    以下是您如何使用此访问者:

    1
    2
    3
    4
    5
    6
    7
    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.");
    }