Recuperando el nombre de la propiedad de la expresión lambda

513

¿Hay una mejor manera de obtener el nombre de la Propiedad cuando se pasa a través de una expresión lambda? Esto es lo que tengo actualmente.

p.ej.

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

Funcionó lanzándolo como una expresión membere solo cuando la propiedad era una cadena. porque no todas las propiedades son cadenas, tuve que usar un objeto, pero luego devolvería una expresión no lineal para ellas.

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
fuente
¿Mejor como en un mejor código? No lo creo. La comprobación de tipos solo se extiende a la expresión general, por lo que realmente necesita las comprobaciones que tiene en tiempo de ejecución. :(
MichaelGG
Sí ... me preguntaba si había una mejor manera de hacerlo, ya que me parecía un poco hacky. Pero si eso es todo, entonces genial. Gracias.
Schotime
He actualizado tu comentario; pero usar una lambda para obtener una cadena para que pueda usar LINQ dinámico me parece que hace las cosas al revés ... si usa una lambda, use una lambda ;-p No tiene que hacer toda la consulta en un solo paso: podría usar OrderBy "regular / lambda", "LINQ / string dinámico" Where, etc.
Marc Gravell
44
Una nota para todos: utilice el MemberExpressionenfoque que se enumera aquí solo para obtener el nombre del miembro, no para obtener el verdadero en MemberInfosí mismo, porque MemberInfono se garantiza que la devolución sea del tipo reflejado en ciertos escenarios "derivados: base". Ver lambda-expression-not-return-expect-memberinfo . Me hizo tropezar una vez. La respuesta aceptada también sufre de esto.
nawfal

Respuestas:

350

Recientemente hice algo muy similar para hacer que un método OnPropertyChanged sea seguro.

Aquí hay un método que devolverá el objeto PropertyInfo para la expresión. Lanza una excepción si la expresión no es una propiedad.

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

El sourceparámetro se utiliza para que el compilador pueda hacer inferencia de tipos en la llamada al método. Puedes hacer lo siguiente

var propertyInfo = GetPropertyInfo(someUserObject, u => u.UserID);
Cameron MacFarland
fuente
66
¿Por qué está allí la última verificación con respecto a TSource? La lambda está fuertemente tipada, así que no creo que sea necesario.
HappyNomad
16
Además, a partir de 2012, la inferencia de tipos funciona bien sin el parámetro fuente.
HappyNomad
44
@HappyNomad Imagine un objeto que tiene como miembro, una instancia de un tercer tipo. u => u.OtherType.OtherTypesPropertycrearía un caso en el que la última declaración esté buscando.
joshperry
55
La última declaración if debería ser: if (type != propInfo.ReflectedType && !type.IsSubclassOf(propInfo.ReflectedType) && !propInfo.ReflectedType.IsAssignableFrom(type))para permitir interfaces también.
Graham King
8
@GrayKing, ¿no sería lo mismo if(!propInfo.ReflectedType.IsAssignableFrom(type))?
Connell
192

Encontré otra forma de hacerlo: tener la fuente y la propiedad fuertemente tipadas e inferir explícitamente la entrada para la lambda. No estoy seguro si esa es la terminología correcta pero aquí está el 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);
}

Y luego llámalo así.

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

y listo funciona.
Gracias a todos.

Schotime
fuente
44
Esta solución debería actualizarse un poco. Por favor revise el siguiente artículo - aquí hay un enlace
Pavel Cermak
1
Es solo una opción si hace ASP.Net MVC y solo para la capa de interfaz de usuario (HtmlHelper).
Marc
3
a partir de c # 6.0 que puede usarGetInfo(nameof(u.UserId))
Vladislav
1
En net core tuve que usar esto:var name = ((MemberExpression) ((UnaryExpression) accessor.Body).Operand).Member.Name
Falk
147

Estaba jugando con lo mismo y resolví esto. No se ha probado completamente, pero parece manejar el problema con los tipos de valor (el problema de expresión no lineal que encontró)

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
fuente
2
probé esto recientemente (de otra pregunta ), descubrí que no maneja subpropiedades: o => o.Thing1.Thing2regresaría Thing2, no Thing1.Thing2, lo cual es incorrecto si está tratando de usarlo en EntityFramework incluye
drzaus
1
AKA (field.Body is 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;
}

Esto maneja expresiones miembro y unarias. La diferencia es que obtendrá un UnaryExpressionsi su expresión representa un tipo de valor, mientras que obtendrá un MemberExpressionsi su expresión representa un tipo de referencia. Todo se puede convertir a un objeto, pero los tipos de valor deben estar encuadrados. Por eso existe la UnaryExpression. Referencia.

En aras de la legibilidad (@Jowen), aquí hay un equivalente ampliado:

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
fuente
@flem, omito <TField> por legibilidad, ¿hay algún problema? LambdaExpressions.GetName <Basket> (m => m.Quantity)
Soren
1
@soren Estoy seguro de que alguien más sintonizado que yo puede sugerirle que está abriendo su código al potencial de boxeo / unboxing innecesario al pasar expresiones de tipos de valor, pero debido a que la expresión nunca se compila y evalúa en este método, Probablemente no sea un problema.
Paul Fleming
30

Con coincidencia de patrones 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());
    }
}

Ejemplo:

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

[Actualización] C # 8 coincidencia de patrones:

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
fuente
20

Esta es una implementación general para obtener el nombre de cadena de campos / propiedades / indexadores / métodos / métodos de extensión / delegados de struct / class / interface / delegate / array. He probado con combinaciones de variantes estáticas / de instancia y no 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);
}

Esto también se puede escribir en un whilebucle 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");
        }
    }
}

Me gusta el enfoque recursivo, aunque el segundo podría ser más fácil de leer. Uno puede llamarlo así:

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 el último miembro.

Nota:

  1. En el caso de expresiones encadenadas como A.B.C, se devuelve "C".

  2. Esto no funciona con consts, indexadores de matriz o enums (imposible de cubrir todos los casos).

nawfal
fuente
19

Hay un caso extremo cuando se trata de Array.Longitud. Mientras 'Longitud' está expuesta como una propiedad, no puede usarla en ninguna de las soluciones propuestas 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);
}

Ahora ejemplo de uso:

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

Si PropertyNameFromUnaryExprno se verificaba ArrayLength, "someArray" se imprimiría en la consola (el compilador parece generar acceso directo al campo Longitud de respaldo , como una optimización, incluso en Depuración, por lo tanto, el caso especial).

kornman00
fuente
16

Aquí hay una actualización del método propuesto por Cameron . El primer parámetro no es obligatorio.

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

Puedes hacer lo siguiente:

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

Métodos de extensión:

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

Usted puede:

SomeType someInstance = null;
string propName = someInstance.NameOfProperty(i => i.Length);
PropertyInfo propInfo = someInstance.GetPropertyInfo(i => i.Length);
Adrian
fuente
No, él no inferirá ucomo algún tipo, no puede hacerlo porque no hay un tipo para inferir. Lo que puedes hacer esGetPropertyInfo<SomeType>(u => u.UserID)
Lucas
14

Descubrí que algunas de las respuestas sugeridas que profundizan en MemberExpression/ UnaryExpressionno capturan / subpropiedades anidadas.

ex) o => o.Thing1.Thing2devuelve en Thing1lugar de Thing1.Thing2.

Esta distinción es importante si está intentando trabajar con EntityFramework DbSet.Include(...).

He descubierto que solo analizar el Expression.ToString()parece funcionar bien, y relativamente rápido. He comparado contra de la UnaryExpressionversión, e incluso conseguir ToStringfuera de la Member/UnaryExpressionpara ver si eso era más rápido, pero la diferencia era insignificante. Corrígeme si es una idea terrible.

El metodo de 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

(Comprobar el delimitador podría incluso ser excesivo)

Demo (LinqPad)

Demostración + código de comparación: https://gist.github.com/zaus/6992590

drzaus
fuente
1
+ 1 muy interesante. ¿Ha seguido utilizando este método en su propio código? funciona bien? ¿Has descubierto casos extremos?
Benjamin Gale
No veo tu idea. Ir por la respuesta que vinculaste o => o.Thing1.Thing2no regresa Thing1como dices pero Thing2. De hecho, su respuesta devuelve algo como lo Thing1.Thing2que puede o no ser deseado.
nawfal
No funciona con el caso korman advierte: stackoverflow.com/a/11006147/661933 . Siempre es mejor evitar los hacks.
nawfal
@nawfal # 1: el problema original es que nunca quieres . Dije que significa el valor de , que es el punto del predicado. Actualizaré la respuesta para reflejar esa intención. Thing1.Thing2Thing1Thing2o.Thing1.Thing2
drzaus
@drzaus lo siento, todavía no te entiendo. Genuinamente tratando de entender. ¿Por qué dirías que otras respuestas aquí regresan Thing1? No creo que vuelva a repetir eso en absoluto.
nawfal
6

Estoy usando un método de extensión para proyectos anteriores a C # 6 y el nombre de () para aquellos que apuntan 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;
    }
}

Y lo llamo así:

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 bien con campos y propiedades.

kalitsov
fuente
5

Bueno, no hay necesidad de llamar .Name.ToString(), pero en general eso es todo, sí. La única consideración que puede necesitar es si x.Foo.Bardebe devolver "Foo", "Bar" o una excepción, es decir, si necesita iterar.

(re comentario) para obtener más información sobre la clasificación flexible, consulte aquí .

Marc Gravell
fuente
Sí ... es solo una cosa de primer nivel, utilizada para generar un enlace de columna de clasificación. p.ej. Si tengo un modelo y quiero mostrar el nombre de la columna para ordenar, puedo usar un enlace fuertemente tipado al objeto para obtener el nombre de la propiedad para la cual linq dinámico no tendrá una vaca. salud.
Schotime
ToStringdebería dar resultados feos para expresiones unarias.
nawfal
3

Creé un método de extensión en ObjectStateEntry para poder marcar las propiedades (de las clases POCO de Entity Framework) como modificadas de forma segura, ya que el método predeterminado solo acepta una cadena. Aquí está mi forma de obtener el nombre de la propiedad:

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

He realizado una INotifyPropertyChangedimplementación similar al método a continuación. Aquí las propiedades se almacenan en un diccionario en la clase base que se muestra a continuación. Por supuesto, no siempre es deseable utilizar la herencia, pero para los modelos de vista, creo que es aceptable y proporciona referencias de propiedades muy claras en las clases de modelos de 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 clase base algo más compleja se muestra a continuación. Maneja la traducción de la expresión lambda al nombre de la propiedad. Tenga en cuenta que las propiedades son realmente pseudo propiedades ya que solo se usan los nombres. Pero aparecerá transparente para el modelo de vista y referencias a las propiedades en el 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;
}
faester
fuente
1
Básicamente estás manteniendo una bolsa de propiedades. No está mal, pero esas llamadas de captadores y establecedores de clase de modelo son un poco más fáciles public bool IsLoading { get { return GetValue(MethodBase.GetCurrentMethod().Name); } set { SetPropertyValue(MethodBase.GetCurrentMethod().Name, value); } }. Podría ser más lento, pero más genérico y directo.
nawfal
En realidad, la implementación de un sistema de propiedad de dependencia simple es más difícil (pero no tan difícil) pero en realidad es mucho más eficaz que la implementación anterior.
Felix K.
3

Esta es otra respuesta:

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;
    }
Memorándum
fuente
1
ModelMetadataExiste en el System.Web.Mvcespacio de nombres. Tal vez no sea adecuado para el caso general
asakura89
3

Dejo esta función si quieres obtener múltiples 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
fuente
3
¿Vas a explicar esto?
1

Aquí hay otra forma de obtener el PropertyInfo basado en esta respuesta. Elimina la necesidad de una instancia 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;
}

Se le puede llamar así:

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

He actualizado la respuesta de @ Cameron para incluir algunas comprobaciones de seguridad contra Convertexpresiones lambda escritas:

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

A partir de .NET 4.0, puede usar ExpressionVisitorpara buscar propiedades:

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

Así es como usas 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
fuente
1

Esto podría ser óptimo

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