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

513

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

Il a fonctionné en le convertissant en expression de membre uniquement lorsque la propriété était une chaîne. parce que toutes les propriétés ne sont pas des chaînes, j'ai dû utiliser un objet, mais cela retournerait une expression unaire 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;
}
Schotime
la source
Mieux que dans un code plus agréable? Je ne pense pas. La vérification de type ne s'étend qu'à l'expression globale, vous avez donc vraiment besoin des vérifications que vous avez en cours d'exécution. :(
MichaelGG
Ouais ... je me demandais juste s'il y avait une meilleure façon de le faire, car c'était un peu hacky pour moi. Mais si c'est ça, cool. Merci.
Schotime
J'ai mis à jour votre commentaire; mais utiliser un lambda pour obtenir une chaîne afin que vous puissiez utiliser LINQ dynamique me semble faire des choses à l'envers ... si vous utilisez un lambda, utilisez un lambda ;-p Vous n'avez pas à faire la requête entière en une seule étape - vous pouvez utiliser "Regular / lambda" OrderBy, "dynamic LINQ / string" Where, etc.
Marc Gravell
4
Une note pour tout le monde: Utilisez l' MemberExpressionapproche répertoriée ici uniquement pour obtenir le nom du membre, pas pour obtenir le réel MemberInfolui-même, car le MemberInforetour n'est pas garanti comme étant du type reflété dans certains scénarios "dervied: base". Voir lambda-expression-not-retourner-attendu-memberinfo . M'a fait trébucher une fois. La réponse acceptée en souffre également.
nawfal

Réponses:

350

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

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

    return propInfo;
}

Le sourceparamètre est utilisé pour que le compilateur puisse faire une inférence de type sur l'appel de méthode. Vous pouvez effectuer les opérations suivantes

var propertyInfo = GetPropertyInfo(someUserObject, u => u.UserID);
Cameron MacFarland
la source
6
Pourquoi y a-t-il la dernière vérification concernant TSource? Le lambda est fortement typé donc je ne pense pas que ce soit nécessaire.
HappyNomad
16
De plus, à partir de 2012, l'inférence de type fonctionne correctement sans le paramètre source.
HappyNomad
4
@HappyNomad Imaginez un objet qui a comme membre, une instance d'un troisième type. u => u.OtherType.OtherTypesPropertycréerait un tel cas que la dernière instruction recherche.
joshperry
5
La dernière instruction if devrait être: if (type != propInfo.ReflectedType && !type.IsSubclassOf(propInfo.ReflectedType) && !propInfo.ReflectedType.IsAssignableFrom(type))pour permettre également les interfaces.
Graham King
8
@GrayKing ne serait-ce pas la même chose que juste if(!propInfo.ReflectedType.IsAssignableFrom(type))?
Connell
192

J'ai trouvé une autre façon de le faire était d'avoir la source et la propriété fortement typées et de déduire explicitement l'entrée 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-le comme ça.

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

et le tour est joué.
Merci a tous.

Schotime
la source
4
Cette solution devrait être un peu mise à jour. Veuillez consulter l'article suivant - voici un lien
Pavel Cermak
1
Ce n'est qu'une option si vous faites ASP.Net MVC et uniquement pour la couche UI (HtmlHelper).
Marc
3
à partir de c # 6.0, vous pouvez utiliserGetInfo(nameof(u.UserId))
Vladislav
1
Dans le noyau net, j'ai dû utiliser ceci:var name = ((MemberExpression) ((UnaryExpression) accessor.Body).Operand).Member.Name
Falk
147

Je jouais avec la même chose et ça a fonctionné. Il n'est pas entièrement testé mais semble gérer le problème avec les types de valeur (le problème d'expression unaire 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;
}
M Thelen
la source
2
essayé récemment (à partir d' une autre question ), a découvert qu'il ne gère pas les sous-propriétés: o => o.Thing1.Thing2retournerait Thing2, non Thing1.Thing2, ce qui est incorrect si vous essayez de l'utiliser dans EntityFramework comprend
drzaus
1
AKA (field.Body is UnaryExpression? ((UnaryExpression) field.Body) .Operand: field.Body) as MemberExpression
51
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 obtiendrez un UnaryExpressionsi votre expression représente un type de valeur tandis que vous obtiendrez un MemberExpressionsi 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.

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

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;
}
Paul Fleming
la source
@flem, j'omets <TField> pour plus de lisibilité, y a-t-il un problème. LambdaExpressions.GetName <Basket> (m => m.Quantity)
Soren
1
@soren Je suis sûr que quelqu'un de plus à l'écoute que moi peut suggérer que vous ouvrez votre code au potentiel de boxe / unboxing inutile lors du passage d'expressions de types valeur, mais parce que l'expression n'est jamais compilée et évaluée dans cette méthode, ce n'est probablement pas un problème.
Paul Fleming
30

Avec la correspondance de motifs 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());
    }
}

Exemple:

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

[Mise à jour] Correspondance de modèle 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())
    };
akhansari
la source
20

Il s'agit d'une implémentation générale pour obtenir le nom de chaîne des champs / propriétés / indexeurs / méthodes / méthodes d'extension / délégués de struct / classe / interface / délégué / tableau. J'ai testé avec des combinaisons de variantes statiques / 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 également être écrite dans une whileboucle simple :

//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, bien que la seconde soit 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. Dans le cas d'expressions chaînées comme A.B.C, "C" est retourné.

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

nawfal
la source
19

Il y a un cas de bord en ce qui concerne la Arraylongueur. Bien que la «longueur» soit exposée en tant que propriété, vous ne pouvez pas 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 ));

S'il PropertyNameFromUnaryExprne vérifiait pas ArrayLength, "someArray" serait imprimé sur la console (le compilateur semble générer un accès direct au champ Backing Length , en tant qu'optimisation, même dans Debug, donc le cas spécial).

kornman00
la source
16

Voici une mise à jour de la méthode proposée par Cameron . Le premier paramètre n'est pas requis.

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

Vous pouvez effectuer les opérations suivantes:

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

Méthodes d'extension:

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

Vous pouvez:

SomeType someInstance = null;
string propName = someInstance.NameOfProperty(i => i.Length);
PropertyInfo propInfo = someInstance.GetPropertyInfo(i => i.Length);
Adrian
la source
Non, il ne déduira pas ucomme un type, il ne peut pas le faire parce qu'il n'y a pas de type à déduire. Ce que vous pouvez faire estGetPropertyInfo<SomeType>(u => u.UserID)
Lucas
14

J'ai trouvé que certaines des réponses suggérées qui forent vers le bas dans la MemberExpression/ UnaryExpressionne capture pas imbriquées / sous -propriétés .

ex) o => o.Thing1.Thing2renvoie Thing1plutôt que Thing1.Thing2.

Cette distinction est importante si vous essayez de travailler avec EntityFramework DbSet.Include(...).

J'ai trouvé que l'analyse syntaxique de Expression.ToString()semble fonctionner correctement et relativement rapidement. Je l'ai comparé à la UnaryExpressionversion, et même en ToStringdescendant Member/UnaryExpressionpour voir si c'était plus rapide, mais la différence était négligeable. Veuillez me corriger si c'est une terrible idée.

La méthode d'extension

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

(La vérification du délimiteur peut même être exagérée)

Démo (LinqPad)

Démonstration + code de comparaison - https://gist.github.com/zaus/6992590

drzaus
la source
1
+ 1 très intéressant. Avez-vous continué à utiliser cette méthode dans votre propre code? ça marche ok? avez-vous découvert des cas de bord?
Benjamin Gale
Je ne vois pas votre idée. Selon la réponse que vous avez liée, vous o => o.Thing1.Thing2ne revenez pas Thing1comme vous le dites, mais Thing2. En fait, votre réponse renvoie quelque chose comme Thing1.Thing2ce qui peut ou non être souhaité.
nawfal
Ne fonctionne pas avec la mise en garde de Korman: stackoverflow.com/a/11006147/661933 . Toujours mieux pour éviter les hacks.
nawfal
@nawfal # 1 - le problème d'origine est que vous voulez Thing1.Thing2 , jamais Thing1. J'ai dit Thing2signifiant la valeur de o.Thing1.Thing2, qui est le point du prédicat. Je mettrai à jour la réponse pour refléter cette intention.
drzaus
@drzaus désolé, je ne vous comprends toujours pas. Vraiment essayer de comprendre. Pourquoi diriez-vous que d'autres réponses ici reviennent Thing1? Je ne pense pas que cela revienne du tout.
nawfal
6

J'utilise une méthode d'extension pour les projets pré C # 6 et le nom de () pour ceux qui ciblent C # 6.

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

Et je l'appelle comme:

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

Cela fonctionne très bien avec les champs et les propriétés.

Kalitsov
la source
5

Eh bien, il n'est pas nécessaire d'appeler .Name.ToString(), mais en gros c'est à peu près tout, oui. La seule considération dont vous pourriez avoir besoin est de savoir si vous devez x.Foo.Barrenvoyer "Foo", "Bar" ou une exception - c.-à-d. Avez-vous besoin d'itérer du tout.

(re comment) pour plus d'informations sur le tri flexible, voir ici .

Marc Gravell
la source
Ouais ... c'est seulement une chose de premier niveau, utilisée pour générer un lien de colonne de tri. par exemple. Si j'ai un modèle et que je souhaite afficher le nom de la colonne à trier, je peux utiliser un lien fortement tapé vers l'objet pour obtenir le nom de la propriété pour laquelle linq dynamique n'aura pas de vache. à votre santé.
Schotime
ToStringdevrait donner des résultats moches pour les expressions unaires.
nawfal
3

J'ai créé une méthode d'extension sur ObjectStateEntry pour pouvoir marquer les propriétés (des classes Entity Framework POCO) comme modifiées de manière sécurisée, car la méthode par défaut n'accepte qu'une chaîne. Voici ma façon d'obtenir le nom de la propriété:

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

J'ai fait l' INotifyPropertyChangedimplémentation similaire à la méthode ci-dessous. Ici, les propriétés sont stockées dans un dictionnaire dans la classe de base indiquée ci-dessous. Il n'est bien sûr pas toujours souhaitable d'utiliser l'héritage, mais pour les modèles de vue, je pense qu'il est acceptable et donne des références de propriété très propres dans les classes de modèles de vue.

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

La classe de base un peu plus complexe est illustrée ci-dessous. Il gère la traduction de l'expression lambda en nom de propriété. Notez que les propriétés sont vraiment des pseudo-propriétés puisque seuls les noms sont utilisés. Mais il apparaîtra transparent pour le modèle de vue et les références aux propriétés sur le modèle de vue.

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;
}
faester
la source
1
Vous entretenez essentiellement un sac de propriété. Pas mal, mais ces appels des getters et des setters de la classe modèle sont un peu plus faciles public bool IsLoading { get { return GetValue(MethodBase.GetCurrentMethod().Name); } set { SetPropertyValue(MethodBase.GetCurrentMethod().Name, value); } }. Pourrait être plus lent, mais plus générique et simple.
nawfal
En fait, l'implémentation d'un système de propriété de dépendance simple est plus difficile (mais pas si difficile) mais en réalité beaucoup plus performant que l'implémentation ci-dessus.
Felix K.
3

Voici une autre réponse:

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;
    }
Note
la source
1
ModelMetadataexiste dans l' System.Web.Mvcespace de noms. Peut-être que ce n'est pas adapté au cas général
asakura89
3

Je laisse cette fonction si vous souhaitez obtenir plusieurs champs:

/// <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);
    }
Carlos Bolivar
la source
3
Allez-vous expliquer cela?
1

Voici une autre façon d'obtenir le PropertyInfo basé sur cette réponse. Il élimine le besoin d'une instance d'objet.

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

Il peut être appelé ainsi:

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

J'ai mis à jour la réponse de @ Cameron pour inclure des contrôles de sécurité par rapport aux Convertexpressions lambda typées:

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

À partir de .NET 4.0, vous pouvez utiliser ExpressionVisitorpour rechercher des propriétés:

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

Voici comment vous utilisez ce visiteur:

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

Cela pourrait être optimal

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;
}
Lucian Popescu
la source
0
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;
}
Stas BZ
la source