Question Récupération du nom de la propriété de l'expression lambda


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

par exemple.

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

Cela a fonctionné en le moulant comme une expression de la membrane seulement quand la propriété était une chaîne. parce que toutes les propriétés ne sont pas des chaînes, j'ai dû utiliser l'objet, mais alors il retournerait une expression unary pour ceux-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;
}

438
2018-03-23 01:24


origine


Réponses:


J'ai récemment fait une chose très similaire pour faire une méthode OnPropertyChanged de type sécurisé.

Voici une méthode qui retournera l'objet PropertyInfo pour l'expression. Il déclenche 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(
            "Expression '{0}' refers to a property that is not from type {1}.",
            propertyLambda.ToString(),
            type));

    return propInfo;
}

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

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

300
2018-03-23 04:14



J'ai trouvé un autre moyen de le faire: avoir la source et la propriété fortement typées et déduire explicitement les entrées pour le lambda. Je ne sais pas si c'est la terminologie 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 puis appelez comme ça.

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

et voila ça marche.
Merci a tous.


162
2018-03-23 05:39



Je jouais avec la même chose et j'ai travaillé dessus. Il n'est pas entièrement testé, mais semble gérer le problème avec les types de valeur (le problème d'expression unary 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;
}

133
2018-05-26 20:04



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

Cela gère les expressions membres et unaires. La différence étant que vous aurez 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 valeur doivent être encadrés. C'est pourquoi l'UnaryExpression existe. Référence.

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

45
2018-06-23 17:27



Il y a un cas limite quand il s'agit de Array.Longueur. While 'Length' est exposé en tant que propriété, vous ne pouvez pas l'utiliser dans l'une 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é pour ArrayLength, "someArray" serait imprimé sur la console (le compilateur semble générer un accès direct au support champ, comme une optimisation, même dans Debug, donc le cas particulier).


19
2018-06-12 23:11



Ceci est une implémentation générale pour obtenir le nom de chaîne des champs / properties / indexers / methods / méthodes d'extension / deleges de struct / class / interface / delegate / array. J'ai testé avec des combinaisons de variantes statiques / d'instance et non génériques / génériques.

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

Cette chose peut être écrite dans un simple while boucle aussi:

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

J'aime l'approche récursive, même si la seconde pourrait être plus facile à lire. On peut l'appeler comme:

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

pour imprimer le dernier membre.

Remarque:

  1. En cas d'expressions enchaînées comme A.B.C, "C" est retourné.

  2. Cela ne fonctionne pas avec consts, indexeurs de tableau ou enums (impossible de couvrir tous les cas).


17
2017-12-12 02:11



maintenant en C # 6 vous pouvez simplement utiliser nom de comme ça nameof(User.UserId) 

qui a de nombreux avantages, parmi eux est que cela est fait à compiler le temps, pas d'exécution.

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


15
2017-12-02 17:10