578 votes

Récupération du nom de la propriété à partir d'une expression lambda

Existe-t-il un meilleur moyen d'obtenir le nom de la propriété lorsqu'il est transmis par une expression lambda ? Voici ce que j'ai actuellement.

eg.

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

Cela a fonctionné en le coulant en tant qu'expression membranaire uniquement lorsque la propriété était une chaîne de caractères. Comme toutes les propriétés ne sont pas des chaînes de caractères, j'ai dû utiliser l'objet, mais cela renvoie une expression nonary pour celles-ci.

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

391voto

Cameron MacFarland Points 27240

J'ai récemment fait une chose très similaire pour créer une méthode OnPropertyChanged sûre pour les types.

Voici une méthode qui renvoie l'objet PropertyInfo pour l'expression. Elle lève une exception si l'expression n'est pas une propriété.

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(
            "Expresion '{0}' refers to a property that is not from type {1}.",
            propertyLambda.ToString(),
            type));

    return propInfo;
}

Le site source est utilisé pour que le compilateur puisse effectuer une inférence de type sur l'appel de méthode. Vous pouvez faire ce qui suit

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

226voto

Schotime Points 6067

J'ai trouvé une autre façon de le faire en ayant la source et la propriété fortement typées et en déduisant explicitement l'entrée pour le lambda. Je ne suis pas sûr que cette terminologie soit correcte, mais voici le résultat.

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

Et ensuite l'appeler comme ça.

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

et voilà, ça marche.
Merci à tous.

155voto

M Thelen Points 760

Je jouais avec la même chose et j'ai créé ceci. Elle n'a pas été entièrement testée, mais elle semble résoudre le problème des types de valeurs (le problème de l'expression non arithmétique que vous avez rencontré).

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

57voto

flem Points 12892
public string GetName<TSource, TField>(Expression<Func<TSource, TField>> Field)
{
    return (Field.Body as MemberExpression ?? ((UnaryExpression)Field.Body).Operand as MemberExpression).Member.Name;
}

Ceci gère les expressions membres et unaires. La différence est que vous obtiendrez un UnaryExpression si votre expression représente un type de valeur alors que vous obtiendrez un MemberExpression si votre expression représente un type de référence. Tout peut être converti en objet, mais les types de valeurs doivent être encadrés. C'est pourquoi l'expression unaire existe. Référence.

Pour des raisons de lisibilité (@Jowen), voici un équivalent élargi :

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

20voto

kornman00 Points 349

Il y a un cas limite quand il s'agit de Array Longueur. Bien que 'Length' soit une propriété exposée, vous ne pouvez l'utiliser dans aucune des solutions proposées précédemment.

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

Maintenant, exemple d'utilisation :

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

Si PropertyNameFromUnaryExpr n'a pas vérifié ArrayLength , "someArray" serait imprimé à la console (le compilateur semble générer un accès direct au backing Length) champ comme une optimisation, même en Debug, donc le cas spécial).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X