497 votos

Recuperar el nombre de propiedad de la expresión lambda

¿Hay una mejor manera de obtener el nombre de la propiedad cuando pasaba por una expresión lambda? Aquí está lo que tengo actualmente.

EG.

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

Funcionó por lo del bastidor como un memberexpression sólo cuando la propiedad era una cadena. Porque no todas las propiedades son cadenas que tuve que usar objeto pero luego volvería un unaryexpression para aquellos.

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

338voto

Cameron MacFarland Puntos 27240

Recientemente hice una cosa muy similar para hacer un tipo seguro método OnPropertyChanged.

Aquí es un método que podrá devolver el objeto PropertyInfo para la expresión. Se produce 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(
            "Expresion '{0}' refers to a property that is not from type {1}.",
            propertyLambda.ToString(),
            type));

    return propInfo;
}

El source parámetro se utiliza para que el compilador puede hacer tipo de inferencia en la llamada al método. Puedes hacer lo siguiente

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

187voto

Schotime Puntos 6067

Encontré otra forma de hacerlo era tener la fuente y la propiedad inflexible y explícitamente inferir la entrada para la lambda. No sé si 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 lo llaman así.

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

y listo funciona.
Gracias por todo.

144voto

M Thelen Puntos 760

Yo estaba jugando con la misma cosa y esta había agitada. Que no sean probado pero parece manejar el tema con los tipos de valor (el tema unaryexpression te topaste con)

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

49voto

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

Se encarga de los estados y expresiones unarias. Con la diferencia de que usted va a obtener un UnaryExpression si su expresión representa un tipo de valor, mientras que usted va a obtener un MemberExpression si su expresión representa un tipo de referencia. Todo lo que se puede convertir en un objeto, pero los tipos de valor que deben estar en cajas. Esta es la razón por la UnaryExpression existe. De referencia.

Por el bien de la legibilidad (@Jowen), he aquí una ampliación equivalente:

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

19voto

kornman00 Puntos 349

Hay un caso extremo cuando se trata de Array.Length. Mientras que la " Longitud " se expone una propiedad, usted puede utilizar en cualquiera de las anteriormente propuestas de soluciones.

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 un ejemplo de uso:

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

Si PropertyNameFromUnaryExpr no compruebe ArrayLength, "someArray" sería impreso en la consola (compilador parece generar acceso directo a la copia de la Longitud del campo, como una optimización, incluso en la Depuración, por lo tanto el caso especial).

Iteramos.com

Iteramos es una comunidad de desarrolladores que busca expandir el conocimiento de la programación mas allá del inglés.
Tenemos una gran cantidad de contenido, y también puedes hacer tus propias preguntas o resolver las de los demás.

Powered by:

X