Recuperando nome da propriedade da expressão lambda

513

Existe uma maneira melhor de obter o nome da propriedade quando transmitida por meio de uma expressão lambda? Aqui está o que eu tenho atualmente.

por exemplo.

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

Funcionou lançando-o como uma expressão membere apenas quando a propriedade era uma string. porque nem todas as propriedades são seqüências de caracteres, eu tive que usar o objeto, mas isso retornaria uma expressão unária para elas.

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
fonte
Melhor como no melhor código? Acho que não. A digitação tipográfica se estende apenas à expressão geral; portanto, você realmente precisa das verificações realizadas no tempo de execução. :(
MichaelGG
Sim ... estava me perguntando se havia uma maneira melhor de fazê-lo, porque me pareceu um pouco hacky. Mas se é isso então legal. obrigado.
Schotime 23/03/09
Eu atualizei o seu comentário; mas usar um lambda para obter uma string para que você possa usar o LINQ dinâmico me parece fazer coisas ao contrário ... se você usa um lambda, use um lambda ;-p Você não precisa fazer a consulta inteira em uma única etapa - você pode usar OrderBy "regular / lambda", "LINQ / string dinâmico" Onde, etc.
Marc Gravell
1
possível duplicata do get-property-name-e-type-usando-lambda-expressão
Nawfal
4
Uma observação para todos: use a MemberExpressionabordagem listada aqui apenas para obter o nome do membro, não para obter o nome realMemberInfo propriamente dito, porque MemberInfonão é garantido que o retorno seja do tipo refletido em certos cenários "dervied: base". Consulte expressão lambda-não-retornando-esperado-memberinfo . Me tropeçou uma vez. A resposta aceita também sofre com isso.
Nawfal 14/12/13

Respostas:

350

Recentemente, fiz uma coisa muito semelhante para tornar um método OnPropertyChanged seguro.

Aqui está um método que retornará o objeto PropertyInfo para a expressão. Ele lança uma exceção se a expressão não for uma propriedade.

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

O sourceparâmetro é usado para que o compilador possa fazer inferência de tipo na chamada do método. Você pode fazer o seguinte

var propertyInfo = GetPropertyInfo(someUserObject, u => u.UserID);
Cameron MacFarland
fonte
6
Por que a última verificação referente ao TSource está lá? O lambda é fortemente digitado, então não acho que seja necessário.
HappyNomad
16
Além disso, a partir de 2012, a inferência de tipo funciona bem sem o parâmetro source.
HappyNomad
4
@HappyNomad Imagine um objeto que tenha como membro, uma instância de um terceiro tipo. u => u.OtherType.OtherTypesPropertycriaria um caso que a última instrução está verificando.
Joshperry
5
A última declaração if deve ser: if (type != propInfo.ReflectedType && !type.IsSubclassOf(propInfo.ReflectedType) && !propInfo.ReflectedType.IsAssignableFrom(type))permitir interfaces também.
Graham King
8
@GrayKing não seria o mesmo que apenas if(!propInfo.ReflectedType.IsAssignableFrom(type))?
Connell
192

Eu descobri outra maneira de fazer isso: digitar fortemente a fonte e a propriedade e inferir explicitamente a entrada para o lambda. Não tenho certeza se essa é a terminologia correta, mas aqui está o resultado.

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 então chame assim.

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

e pronto, funciona.
Obrigado a todos.

Schotime
fonte
4
Esta solução deve ser um pouco atualizada. Por favor, verifique o seguinte artigo - aqui está um link
Pavel Cermak 2/14
1
É apenas uma opção se você fizer o ASP.Net MVC e apenas para a camada da interface do usuário (HtmlHelper).
Marc
3
a partir de c # 6.0 você pode usarGetInfo(nameof(u.UserId))
Vladislav
1
No net core eu tive que usar isso: #var name = ((MemberExpression) ((UnaryExpression) accessor.Body).Operand).Member.Name
Falk
146

Eu estava brincando com a mesma coisa e resolvi isso. Não foi totalmente testado, mas parece lidar com o problema com tipos de valor (o problema de não expressão que você encontrou)

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
fonte
2
tentei isso recentemente (a partir de uma outra questão ), descobri que não manipula subpropriedades: o => o.Thing1.Thing2voltaria Thing2, não Thing1.Thing2, o que é incorreto, se você está tentando usá-lo em EntityFramework inclui
drzaus
1
AKA (? Field.Body é UnaryExpression ((UnaryExpression) field.Body) .Operand: field.Body) como 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;
}

Isso lida com expressões de membros e unárias. A diferença é que você receberá um UnaryExpressionse sua expressão representar um tipo de valor, enquanto você receberá um MemberExpressionse sua expressão representar um tipo de referência. Tudo pode ser convertido em um objeto, mas os tipos de valor devem ser encaixotados. É por isso que a UnaryExpression existe.Referência.

Para facilitar a leitura (@Jowen), aqui está um equivalente expandido:

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
fonte
@ Flem, omito <TField> por legibilidade, existe algum problema. LambdaExpressions.GetName <Basket> (m => m.Quantity)
Soren
1
@soren Tenho certeza de que alguém mais sintonizado do que eu pode sugerir que você esteja abrindo seu código para o potencial de boxe / unboxing desnecessário ao passar expressões de tipos de valor, mas porque a expressão nunca é compilada e avaliada neste método, provavelmente não é um problema.
Paul Fleming
30

Com a correspondência de padrões 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());
    }
}

Exemplo:

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

[Atualização] Correspondência de padrão 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
fonte
20

Esta é uma implementação geral para obter o nome da sequência de campos / propriedades / indexadores / métodos / métodos de extensão / delegados de struct / class / interface / delegate / array. Eu testei com combinações de static / instance e variantes não genéricas / genéricas.

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

Isso também pode ser escrito em um whileloop simples :

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

Gosto da abordagem recursiva, embora a segunda possa ser mais fácil de ler. Pode-se chamar assim:

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

para imprimir o último membro.

Nota:

  1. No caso de expressões encadeadas como A.B.C, "C" é retornado.

  2. Isso não funciona com consts, indexadores de matriz ou enums (impossível de cobrir todos os casos).

nawfal
fonte
19

Há um caso de ponta quando se trata de Array.Length. Enquanto 'Comprimento' é exposto como uma propriedade, você não pode usá-lo em nenhuma das soluções propostas anteriormente.

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

Agora exemplo de uso:

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

Se PropertyNameFromUnaryExprnão fosse verificado ArrayLength, "someArray" seria impresso no console (o compilador parece gerar acesso direto ao campo Comprimento de backup , como uma otimização, mesmo em Debug, portanto, no caso especial).

kornman00
fonte
16

Aqui está uma atualização do método proposto por Cameron . O primeiro parâmetro não é necessário.

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

Você pode fazer o seguinte:

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

Métodos de extensão:

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

Você pode:

SomeType someInstance = null;
string propName = someInstance.NameOfProperty(i => i.Length);
PropertyInfo propInfo = someInstance.GetPropertyInfo(i => i.Length);
Adrian
fonte
Não, ele não deduz ucomo algum tipo, ele não pode fazer isso porque não há tipo para deduzir. O que você pode fazer éGetPropertyInfo<SomeType>(u => u.UserID)
Lucas
14

Descobri que algumas das respostas sugeridas que detalham o MemberExpression/UnaryExpression não captam subpropriedades aninhados /.

ex) o => o.Thing1.Thing2retorna em Thing1vez deThing1.Thing2 .

Essa distinção é importante se você estiver tentando trabalhar com o EntityFramework DbSet.Include(...) .

Descobri que apenas analisar o Expression.ToString()parece funcionar bem e comparativamente rapidamente. Comparei-o com a UnaryExpressionversão, e mesmo ToStringsaindo daMember/UnaryExpression para ver se isso era mais rápido, mas a diferença foi insignificante. Por favor, corrija-me se for uma péssima ideia.

O método de extensão

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

(A verificação do delimitador pode até ser um exagero)

Demonstração (LinqPad)

Demonstração + código de comparação - https://gist.github.com/zaus/6992590

drzaus
fonte
1
+ 1 muito interessante. Você continuou usando esse método em seu próprio código? isso funciona ok? você descobriu algum caso extremo?
Benjamin Gale
Não consigo ver sua ideia. Seguir a resposta que você vinculou o => o.Thing1.Thing2não retorna Thing1como você diz, mas Thing2. De fato, sua resposta retorna algo como o Thing1.Thing2que pode ou não ser desejado.
Nawfal # 11/13
Não funciona com o caso korman adverte: stackoverflow.com/a/11006147/661933 . Sempre melhor evitar hacks.
Nawfal # 11/13
@ nawfal # 1 - o problema original é que você deseja Thing1.Thing2 , nunca Thing1. Eu disse Thing2significando o valor de o.Thing1.Thing2, que é o ponto do predicado. Vou atualizar a resposta para refletir essa intenção.
Drzaus #
@drzaus desculpe, eu ainda não estou entendendo você. Tentando genuinamente entender. Por que você diria que outras respostas aqui retornam Thing1? Eu não acho que isso refaça tudo.
Nawfal # 15/13
6

Estou usando um método de extensão para projetos anteriores ao C # 6 e o nome de () para aqueles direcionados ao 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 eu chamo assim:

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

Funciona bem com campos e propriedades.

Kalitsov
fonte
5

Bem, não há necessidade de ligar .Name.ToString(), mas isso é amplamente, sim. A única consideração que você pode precisar é se x.Foo.Bardeve retornar "Foo", "Bar" ou uma exceção - ou seja, você precisa iterar.

(re-comentário) para mais informações sobre classificação flexível, veja aqui .

Marc Gravell
fonte
Sim ... é apenas uma coisa de primeiro nível, usada para gerar um link de coluna de classificação. por exemplo. Se eu tenho um modelo e quero exibir o nome da coluna para classificar, posso usar um link fortemente digitado para o objeto para obter o nome da propriedade para o qual o linq dinâmico não terá uma vaca. Felicidades.
Schotime 23/03/09
ToStringdeve dar resultados feios para expressões unárias.
Nawfal
3

Criei um método de extensão no ObjectStateEntry para poder sinalizar propriedades (das classes POCO do Entity Framework) como modificadas de maneira segura, pois o método padrão aceita apenas uma sequência. Aqui está minha maneira de obter o nome da propriedade:

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
fonte
3

Fiz a INotifyPropertyChangedimplementação semelhante ao método abaixo. Aqui, as propriedades são armazenadas em um dicionário na classe base mostrada abaixo. Obviamente, nem sempre é desejável usar herança, mas para os modelos de exibição, acho que é aceitável e fornece referências de propriedades muito limpas nas classes de modelos de exibição.

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

A classe base um pouco mais complexa é mostrada abaixo. Ele lida com a tradução da expressão lambda para o nome da propriedade. Observe que as propriedades são realmente pseudo-propriedades, pois apenas os nomes são usados. Mas parecerá transparente ao modelo de vista e referências às propriedades no modelo de 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;
}
mais fraco
fonte
1
Você está basicamente mantendo uma sacola de propriedades. Nada mal, mas essas chamadas de getters e setters da classe model são um pouco mais fáceis public bool IsLoading { get { return GetValue(MethodBase.GetCurrentMethod().Name); } set { SetPropertyValue(MethodBase.GetCurrentMethod().Name, value); } }. Pode ser mais lento, mas mais genérico e direto.
Nawfal
A implementação de um sistema simples de propriedades de dependência é mais difícil (mas não tão difícil), mas na verdade é muito mais eficiente do que a implementação acima.
Felix K.
3

Esta é outra resposta:

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;
    }
Memorando
fonte
1
ModelMetadataexiste no System.Web.Mvcespaço de nomes. Talvez ele não está apto para o caso geral
asakura89
3

Deixo esta função se você deseja obter múltiplos campos:

/// <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
fonte
3
Você vai explicar isso?
1

Aqui está outra maneira de obter o PropertyInfo com base nesta resposta. Isso elimina a necessidade de uma instância de objeto.

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

Pode ser chamado assim:

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

Atualizei a resposta de @ Cameron para incluir algumas verificações de segurança contra Convertexpressões lambda digitadas:

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
fonte
1

A partir do .NET 4.0, você pode usar ExpressionVisitorpara encontrar propriedades:

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

Aqui está como você usa este visitante:

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
fonte
1

Isso pode ser ideal

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