it-swarm.com.de

Abrufen des Eigenschaftsnamens aus dem Lambda-Ausdruck

Gibt es eine bessere Möglichkeit, den Eigenschaftsnamen zu ermitteln, wenn er über einen Lambda-Ausdruck übergeben wird? Hier ist, was ich derzeit habe.

z.B.

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

Es funktionierte, indem es nur dann als Memberexpression umgewandelt wurde, wenn es sich bei der Eigenschaft um einen String handelte. da nicht alle Eigenschaften Zeichenfolgen sind, musste ich object verwenden, aber dann würde es für diese einen unaryexpression zurückgeben.

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

Ich habe eine andere Möglichkeit gefunden, dies zu tun, indem Sie die Quelle und die Eigenschaft stark typisiert haben und explizit auf die Eingabe für das Lambda schließen. Ich bin nicht sicher, ob das die richtige Terminologie ist, aber hier ist das Ergebnis.

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

Und dann nenne es so.

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

und voila es funktioniert.
Danke an alle.

180
Schotime

Vor kurzem habe ich eine sehr ähnliche Aktion durchgeführt, um eine typsichere OnPropertyChanged-Methode zu erstellen.

Hier ist eine Methode, die das PropertyInfo-Objekt für den Ausdruck zurückgibt. Es wird eine Ausnahme ausgelöst, wenn der Ausdruck keine Eigenschaft ist.

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

Der Parameter source wird verwendet, damit der Compiler beim Methodenaufruf Rückschlüsse ziehen kann. Sie können Folgendes tun

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

Ich habe mit der gleichen Sache rumgespielt und mich darum gekümmert. Es ist nicht vollständig getestet, scheint aber das Problem mit den Werttypen zu lösen (das Problem mit dem unaryexpression, auf das Sie gestoßen sind).

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;
}
142
M Thelen
public string GetName<TSource, TField>(Expression<Func<TSource, TField>> Field)
{
    return (Field.Body as MemberExpression ?? ((UnaryExpression)Field.Body).Operand as MemberExpression).Member.Name;
}

Dies behandelt Member- und Unary-Ausdrücke. Der Unterschied besteht darin, dass Sie ein UnaryExpression erhalten, wenn Ihr Ausdruck einen Werttyp darstellt, während Sie ein MemberExpression erhalten, wenn Ihr Ausdruck einen Referenztyp darstellt. Alles kann in ein Objekt umgewandelt werden, Werttypen müssen jedoch mit einem Kästchen versehen werden. Aus diesem Grund gibt es den UnaryExpression. Referenz.

Aus Gründen der Lesbarkeit (@Jowen) ist hier ein erweitertes Äquivalent:

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;
}
49
Paul Fleming

jetzt können Sie in C # 6 einfach nameof wie folgt verwenden nameof(User.UserId)

das hat viele Vorteile, unter anderem, dass dies zur Kompilierungszeit und nicht zur Laufzeit erfolgt.

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

21
Maslow

Mit C # 7 Pattern Matching:

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

Beispiel:

public static RouteValueDictionary GetInfo<T>(this HtmlHelper html, 
    Expression<Func<T, object>> action) where T : class
{
    var name = action.GetMemberName();
    return GetInfo(html, name);
}
21
akhansari

Es gibt einen Edge-Fall, wenn es um Array. Length geht. Während 'Length' als Eigenschaft verfügbar ist, können Sie sie in keiner der zuvor vorgeschlagenen Lösungen verwenden.

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

Jetzt Beispielverwendung:

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

Wenn PropertyNameFromUnaryExpr nicht nach ArrayLength gesucht hat, wird "someArray" auf der Konsole ausgegeben (der Compiler scheint einen direkten Zugriff auf die Backing-Länge zu generieren Feld als Optimierung, auch im Debug, also im Sonderfall).

19
kornman00

Dies ist eine allgemeine Implementierung, um den Zeichenfolgennamen der Felder/Eigenschaften/Indexer/Methoden/Erweiterungsmethoden/Delegaten von struct/class/interface/delegate/array abzurufen. Ich habe mit Kombinationen aus statischen/Instanz- und nicht generischen/generischen Varianten getestet.

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

Dieses Ding kann auch in einer einfachen while Schleife geschrieben werden:

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

Ich mag den rekursiven Ansatz, obwohl der zweite möglicherweise leichter zu lesen ist. Man kann es so nennen:

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

um das letzte Mitglied zu drucken.

Hinweis:

  1. Bei verketteten Ausdrücken wie A.B.C, "C" wird zurückgegeben.

  2. Dies funktioniert nicht mit consts, Array-Indexern oder enums (unmöglich, alle Fälle abzudecken).

19
nawfal

Hier ist ein Update zu von Cameron vorgeschlagene Methode . Der erste Parameter ist nicht erforderlich.

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

Sie können Folgendes tun:

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

Erweiterungsmethoden:

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

Du kannst:

SomeType someInstance = null;
string propName = someInstance.NameOfProperty(i => i.Length);
PropertyInfo propInfo = someInstance.GetPropertyInfo(i => i.Length);
16
Adrian

Ich habe festgestellt, dass einige der vorgeschlagenen Antworten , die in die MemberExpression/UnaryExpression -Drilldowns eingehen, keine verschachtelten/untergeordneten Eigenschaften erfassen.

ex) o => o.Thing1.Thing2 gibt Thing1 und nicht Thing1.Thing2 zurück.

Diese Unterscheidung ist wichtig, wenn Sie versuchen, mit EntityFramework DbSet.Include(...) zu arbeiten.

Ich habe festgestellt, dass nur das Parsen der Expression.ToString() gut und vergleichsweise schnell zu funktionieren scheint. Ich habe es mit der UnaryExpression Version verglichen und sogar ToString aus dem Member/UnaryExpression Herausgeholt, um zu sehen, ob das schneller war, aber der Unterschied war vernachlässigbar. Bitte korrigieren Sie mich, wenn dies eine schreckliche Idee ist.

Die Verlängerungsmethode

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

(Das Überprüfen auf das Trennzeichen kann sogar zu viel sein.)

Demo (LinqPad)

Demo + Vergleichscode - https://Gist.github.com/zaus/699259

14
drzaus

Ich verwende eine Erweiterungsmethode für Projekte vor C # 6 und das nameof () für diejenigen, die auf C # 6 abzielen.

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

Und ich nenne es so:

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

Es funktioniert gut mit Feldern und Eigenschaften.

6
kalitsov

Nun, es gibt keine Notwendigkeit, .Name.ToString() aufzurufen, aber im Großen und Ganzen geht es darum, ja. Die einzige Überlegung, die Sie möglicherweise benötigen, ist, ob x.Foo.Bar sollte "Foo", "Bar" oder eine Ausnahme zurückgeben - d. h. müssen Sie überhaupt iterieren.

Weitere Informationen zum flexiblen Sortieren finden Sie unter hier .

5
Marc Gravell

Ich habe die INotifyPropertyChanged -Implementierung ähnlich der folgenden Methode durchgeführt. Hier werden die Eigenschaften in einem Dictionary in der unten gezeigten Basisklasse gespeichert. Es ist natürlich nicht immer wünschenswert, Vererbung zu verwenden, aber für Ansichtsmodelle halte ich es für akzeptabel und gebe sehr saubere Eigenschaftsreferenzen in den Ansichtsmodellklassen.

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

Die etwas komplexere Basisklasse ist unten dargestellt. Es behandelt die Übersetzung vom Lambda-Ausdruck zum Eigenschaftsnamen. Beachten Sie, dass die Eigenschaften wirklich Pseudoeigenschaften sind, da nur die Namen verwendet werden. Es wird jedoch für das Ansichtsmodell und Verweise auf die Eigenschaften des Ansichtsmodells transparent angezeigt.

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

Dies ist eine andere Antwort:

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

Ich habe eine Erweiterungsmethode für ObjectStateEntry erstellt, um Eigenschaften (von Entity Framework-POCO-Klassen) als typsicher zu kennzeichnen, da die Standardmethode nur eine Zeichenfolge akzeptiert. Hier ist meine Art, den Namen von der Unterkunft zu erhalten:

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);
}
3
Anders

Ich lasse diese Funktion, wenn Sie mehrere Felder erhalten möchten:

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

Ich habe @ Camerons Antwort aktualisiert, um einige Sicherheitsprüfungen für Convert typisierte Lambda-Ausdrücke einzuschließen:

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

Ab .NET 4.0 können Sie ExpressionVisitor verwenden, um Eigenschaften zu finden:

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

So verwenden Sie diesen Besucher:

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

Hier ist eine andere Möglichkeit, die PropertyInfo anhand dieser Antwort zu ermitteln . Sie macht eine Objektinstanz überflüssig.

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

Man kann es so nennen:

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