Recupero del nome della proprietà dall'espressione lambda


513

C'è un modo migliore per ottenere il nome della proprietà quando viene passato tramite un'espressione lambda? Ecco quello che ho attualmente.

per esempio.

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

Ha funzionato lanciandolo come un'espressione membere solo quando la proprietà era una stringa. poiché non tutte le proprietà sono stringhe, ho dovuto usare l'oggetto, ma poi sarebbe tornata una espressione unaria per quelle.

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

Meglio come nel codice più bello? Io non la penso così. Il typechecking si estende solo all'espressione generale, quindi hai davvero bisogno dei controlli che hai in fase di esecuzione. :(
MichaelGG,

Sì ... mi chiedevo solo se ci fosse un modo migliore per farlo, dato che mi sembrava un po 'confuso. Ma se è così, allora fico. Grazie.
Schotime,

Ho aggiornato il tuo commento; ma usare un lambda per ottenere una stringa in modo da poter usare LINQ dinamico mi sembra di fare le cose all'indietro ... se usi un lambda, usa un lambda ;-p Non devi fare l'intera query in un solo passaggio - potresti usare "Regular / lambda" OrderBy, "LINQ dinamico / stringa" Dove, ecc.
Marc Gravell


4
Una nota per tutti: utilizzare l' MemberExpressionapproccio elencato qui solo per ottenere il nome del membro, non per ottenere l'effettivo MemberInfostesso, poiché MemberInfonon è garantito che il reso sia del tipo riflesso in alcuni scenari "dervied: base". Vedi lambda-expression-not-return-expected-memberinfo . Mi ha inciampato una volta. Anche la risposta accettata ne soffre.
nawfal,

Risposte:


350

Di recente ho fatto una cosa molto simile per rendere sicuro un metodo OnPropertyChanged di tipo.

Ecco un metodo che restituirà l'oggetto PropertyInfo per l'espressione. Genera un'eccezione se l'espressione non è una proprietà.

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

Il sourceparametro viene utilizzato in modo che il compilatore possa fare l'inferenza di tipo sulla chiamata del metodo. Puoi fare quanto segue

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

6
Perché è presente l'ultimo controllo relativo a TSource? La lambda è fortemente tipizzata, quindi non credo sia necessario.
HappyNomad,

16
Inoltre, a partire dal 2012, l'inferenza del tipo funziona bene senza il parametro source.
HappyNomad,

4
@HappyNomad Immagina un oggetto che ha come membro un'istanza di terzo tipo. u => u.OtherType.OtherTypesPropertycreerebbe un caso del genere che l'ultima istruzione sta verificando.
joshperry,

5
L'ultima istruzione if dovrebbe essere: if (type != propInfo.ReflectedType && !type.IsSubclassOf(propInfo.ReflectedType) && !propInfo.ReflectedType.IsAssignableFrom(type))per consentire anche le interfacce.
Graham King,

8
@GrayKing non sarebbe lo stesso del solo if(!propInfo.ReflectedType.IsAssignableFrom(type))?
Connell,

192

Ho trovato un altro modo in cui è possibile farlo: avere la fonte e la proprietà fortemente tipizzate e inferire esplicitamente l'input per la lambda. Non sono sicuro che sia la terminologia corretta, ma qui è il risultato.

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

E poi chiamalo così.

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

e voilà funziona.
Ringrazia tutti.


4
Questa soluzione dovrebbe essere un po 'aggiornata. Si prega di controllare il seguente articolo - ecco un link
Pavel Cermak,

1
È solo un'opzione se si esegue ASP.Net MVC e solo per il livello UI (HtmlHelper).
Marc

3
a partire da c # 6.0 puoi usareGetInfo(nameof(u.UserId))
Vladislav il

1
Nel core netto ho dovuto usare questo:var name = ((MemberExpression) ((UnaryExpression) accessor.Body).Operand).Member.Name
Falk

147

Stavo giocando con la stessa cosa e ho lavorato su. Non è stato completamente testato ma sembra gestire il problema con i tipi di valore (il problema unaryexpression che hai riscontrato)

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

2
provato di recente (da un'altra domanda ), ho scoperto che non gestisce sottoproprietà: o => o.Thing1.Thing2restituirebbe Thing2no Thing1.Thing2, il che non è corretto se si sta tentando di utilizzarlo in EntityFramework include
drzaus

1
AKA (field.Body is UnaryExpression? ((UnaryExpression) field.Body) .Operando: field.Body) come 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;
}

Gestisce le espressioni membro e unario. La differenza è che otterrai un UnaryExpressionse la tua espressione rappresenta un tipo di valore mentre otterrai un MemberExpressionse la tua espressione rappresenta un tipo di riferimento. Tutto può essere trasmesso a un oggetto, ma i tipi di valore devono essere inscatolati. Ecco perché esiste UnaryExpression. Riferimento.

Per motivi di leggibilità (@Jowen), ecco un equivalente espanso:

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

@flem, ometto <TField> per leggibilità, c'è qualche problema. LambdaExpressions.GetName <Basket> (m => m.Quantity)
Soren

1
@soren Sono sicuro che qualcuno più sintonizzato di me potrebbe suggerire che stai aprendo il tuo codice al potenziale di boxing / unboxing non necessario quando si passano espressioni di tipi di valore, ma poiché l'espressione non viene mai compilata e valutata in questo metodo, probabilmente non è un problema.
Paul Fleming,

30

Con la corrispondenza del modello 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());
    }
}

Esempio:

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

[Aggiornamento] Corrispondenza del modello 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())
    };


20

Questa è un'implementazione generale per ottenere il nome stringa di campi / proprietà / indicizzatori / metodi / metodi di estensione / delegati di struct / class / interface / delegate / array. Ho testato con combinazioni di varianti statiche / di istanza e non generiche / generiche.

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

Questa cosa può essere scritta anche in un semplice whileciclo:

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

Mi piace l'approccio ricorsivo, anche se il secondo potrebbe essere più facile da leggere. Si può chiamare come:

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

per stampare l'ultimo membro.

Nota:

  1. In caso di espressioni concatenate come A.B.C, viene restituita "C".

  2. Questo non funziona con consts, indicizzatori di array o enums (impossibile coprire tutti i casi).


19

C'è un caso limite quando si tratta di Array.Length. Mentre "Lunghezza" è esposto come proprietà, non è possibile utilizzarlo in nessuna delle soluzioni precedentemente proposte.

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

Ora esempio di utilizzo:

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

Se PropertyNameFromUnaryExprnon controllato ArrayLength, "someArray" verrebbe stampato sulla console (il compilatore sembra generare accesso diretto al campo Lunghezza di supporto , come ottimizzazione, anche in Debug, quindi nel caso speciale).


16

Ecco un aggiornamento al metodo proposto da Cameron . Il primo parametro non è richiesto.

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

Puoi fare quanto segue:

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

Metodi di estensione:

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

Puoi:

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

No, non inferirà ucome un certo tipo, non può farlo perché non esiste un tipo da inferire. Quello che puoi fare èGetPropertyInfo<SomeType>(u => u.UserID)
Lucas

14

Ho scoperto che alcune delle risposte suggerite che analizzano in dettaglio MemberExpression/ UnaryExpressionnon acquisiscono nidificati / sottoproprietà.

ex) o => o.Thing1.Thing2restituisce Thing1anziché Thing1.Thing2.

Questa distinzione è importante se stai cercando di lavorare con EntityFramework DbSet.Include(...).

Ho scoperto che il solo analizzare Expression.ToString()sembra funzionare bene e relativamente velocemente. L'ho confrontato con la UnaryExpressionversione, e anche scendendo ToStringdal Member/UnaryExpressionper vedere se era più veloce, ma la differenza era trascurabile. Per favore, correggimi se questa è un'idea terribile.

Il metodo di estensione

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

(Il controllo del delimitatore potrebbe anche essere eccessivo)

Demo (LinqPad)

Dimostrazione + codice di confronto - https://gist.github.com/zaus/6992590


1
+ 1 molto interessante. Hai continuato a utilizzare questo metodo nel tuo codice? funziona bene? hai scoperto casi limite?
Benjamin Gale,

Non riesco a vedere la tua idea. Seguire la risposta che hai collegato o => o.Thing1.Thing2non ritorna Thing1come dici ma Thing2. In effetti la tua risposta restituisce qualcosa di simile Thing1.Thing2che può o non può essere desiderato.
nawfal,

Non funziona con i casi di cautele Korman: stackoverflow.com/a/11006147/661933 . Sempre meglio evitare gli hack.
nawfal,

@nawfal # 1 - il problema originale è che vuoi Thing1.Thing2 , mai Thing1. Ho detto che Thing2significa il valore di o.Thing1.Thing2, che è il punto del predicato. Aggiornerò la risposta per riflettere tale intenzione.
drzaus,

@drzaus scusa non ti sto ancora prendendo. Cercando sinceramente di capire. Perché diresti che altre risposte qui ritornano Thing1? Non credo che lo ripeti affatto.
nawfal,

6

Sto usando un metodo di estensione per progetti pre C # 6 e il nome di () per quelli destinati a 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;
    }
}

E lo chiamo come:

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

Funziona bene con entrambi i campi e le proprietà.


5

Bene, non c'è bisogno di chiamare .Name.ToString(), ma in generale questo è tutto, sì. L'unica considerazione di cui potresti aver bisogno è se x.Foo.Bardeve restituire "Foo", "Bar" o un'eccezione, ovvero devi ripetere l'iterazione.

(re comment) per ulteriori informazioni sull'ordinamento flessibile, vedere qui .


Sì ... è solo una cosa di primo livello, utilizzata per generare un collegamento alla colonna di ordinamento. per esempio. Se ho un modello e voglio visualizzare il nome della colonna in base al quale ordinare, posso utilizzare un collegamento fortemente tipizzato all'oggetto per ottenere il nome della proprietà per cui linq dinamico non avrà una vacca. Saluti.
Schotime,

ToStringdovrebbe dare risultati brutti per le espressioni unarie.
nawfal,

3

Ho creato un metodo di estensione su ObjectStateEntry per poter contrassegnare le proprietà (delle classi POCO di Entity Framework) come modificate in modo sicuro, poiché il metodo predefinito accetta solo una stringa. Ecco il mio modo di ottenere il nome dalla proprietà:

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

Ho eseguito l' INotifyPropertyChangedimplementazione in modo simile al metodo seguente. Qui le proprietà sono memorizzate in un dizionario nella classe base mostrata di seguito. Naturalmente non è sempre desiderabile utilizzare l'ereditarietà, ma per i modelli di vista penso che sia accettabile e fornisca riferimenti di proprietà molto chiari nelle classi del modello di vista.

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 base un po 'più complessa è mostrata di seguito. Gestisce la traduzione dall'espressione lambda al nome della proprietà. Si noti che le proprietà sono in realtà proprietà pseudo poiché vengono utilizzati solo i nomi. Apparirà trasparente al modello di vista e riferimenti alle proprietà sul modello di vista.

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

1
Fondamentalmente stai mantenendo una borsa di proprietà. Non male, ma quelle chiamate da getter e setter della classe del modello sono un po 'più facili public bool IsLoading { get { return GetValue(MethodBase.GetCurrentMethod().Name); } set { SetPropertyValue(MethodBase.GetCurrentMethod().Name, value); } }. Potrebbe essere più lento, ma più generico e semplice.
nawfal,

In realtà implementare un semplice sistema di proprietà di dipendenza è più difficile (ma non così difficile) ma in realtà molto più performante dell'implementazione di cui sopra.
Felix K.,

3

Questa è un'altra risposta:

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

1
ModelMetadataè presente nello System.Web.Mvcspazio dei nomi. Forse non è adatto al caso generale
asakura89,

3

Lascio questa funzione se si desidera ottenere campi multipli:

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

3
Lo spiegherai?

1

Ecco un altro modo per ottenere PropertyInfo basato su questa risposta. Elimina la necessità di un'istanza di oggetto.

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

Può essere chiamato così:

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

1

Ho aggiornato la risposta di @ Cameron per includere alcuni controlli di sicurezza contro Convertle espressioni lambda digitate:

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

A partire da .NET 4.0 è possibile utilizzare ExpressionVisitorper trovare proprietà:

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

Ecco come usi questo visitatore:

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

Questo potrebbe essere ottimale

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

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;
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.