2009-11-03 5 views
29

Estoy desarrollando una API que usa expresiones lambda para especificar propiedades. Estoy usando esta famosa pieza de código similar a ésta (esto se simplifica e incompleta, sólo para dejar claro lo que estoy hablando):C#: obteniendo los nombres de las propiedades en una cadena de la expresión lambda

public void Foo<T, P>(Expression<Func<T, P>> action) 
{ 
    var expression = (MemberExpression)action.Body; 
    string propertyName = expression.Member.Name; 
    // ... 
} 

que se llamará así:

Foo((String x) => x.Length); 

Ahora me gustaría especificar una ruta de propiedad por el encadenamiento de los nombres de propiedades, así:

Foo((MyClass x) => x.Name.Length); 

Foo debe ser capaz de dividir la ruta en sus nombres de propiedad ("Name" y "Length"). ¿Hay alguna manera de hacer esto con un esfuerzo razonable?


Hay una somehow similar looking question, pero creo que están tratando de combinar las expresiones lambda allí.

Another question también se trata de nombres de propiedades anidadas, pero realmente no entiendo de qué están hablando.

Respuesta

27

¿Algo como esto?

public void Foo<T, P>(Expression<Func<T, P>> expr) 
{ 
    MemberExpression me; 
    switch (expr.Body.NodeType) 
    { 
     case ExpressionType.Convert: 
     case ExpressionType.ConvertChecked: 
      var ue = expr.Body as UnaryExpression; 
      me = ((ue != null) ? ue.Operand : null) as MemberExpression; 
      break; 
     default: 
      me = expr.Body as MemberExpression; 
      break; 
    } 

    while (me != null) 
    { 
     string propertyName = me.Member.Name; 
     Type propertyType = me.Type; 

     Console.WriteLine(propertyName + ": " + propertyType); 

     me = me.Expression as MemberExpression; 
    } 
} 
+1

Wow, esto funciona, y es bastante simple. ¡Muchas gracias! –

+1

@StefanSteinegger Pregunta anterior, lo sé ... pero si solo son los nombres que necesita, 'expr.ToString(). Split ('.'). Skip (1)' sería aún más simple :) – asgerhallas

+2

@asgerhallas: usted puede agregar otra respuesta. –

11

vieja pregunta, lo sé ... pero si es sólo los nombres que necesita, de una manera aún más sencilla de hacerlo es:

expr.ToString().Split('.').Skip(1) 

EDIT:

public class A 
{ 
    public B Property { get; set; } 
} 

public class B 
{ 
    public C field; 
} 

[Fact] 
public void FactMethodName() 
{ 
    var exp = (Expression<Func<A, object>>) (x => x.Property.field); 
    foreach (var part in exp.ToString().Split('.').Skip(1)) 
     Console.WriteLine(part); 

    // Output: 
    // Property 
    // field 
} 
+0

Hmmm, eso no funcionó para mí ('.ToString' solo dio el último nombre de propiedad). ¿Tienes una muestra de código más grande con el uso? – Pat

+0

@Pat He editado algunos códigos de trabajo. Espero que ayude. Aunque un poco tarde :) – asgerhallas

+0

'ToString' no funcionará en caso de que los tipos de valores estén encuadrados, además de ser terriblemente lento. Solo ten cuidado – nawfal

11

Jugué un poco con ExpressionVisitor:

public static class PropertyPath<TSource> 
{ 
    public static IReadOnlyList<MemberInfo> Get<TResult>(Expression<Func<TSource, TResult>> expression) 
    { 
     var visitor = new PropertyVisitor(); 
     visitor.Visit(expression.Body); 
     visitor.Path.Reverse(); 
     return visitor.Path; 
    } 

    private class PropertyVisitor : ExpressionVisitor 
    { 
     internal readonly List<MemberInfo> Path = new List<MemberInfo>(); 

     protected override Expression VisitMember(MemberExpression node) 
     { 
      if (!(node.Member is PropertyInfo)) 
      { 
       throw new ArgumentException("The path can only contain properties", nameof(node)); 
      } 

      this.Path.Add(node.Member); 
      return base.VisitMember(node); 
     } 
    } 
} 

Uso:

var path = string.Join(".", PropertyPath<string>.Get(x => x.Length).Select(p => p.Name)); 
+0

Gracias por ExpressionVisitor. Esta fue una solución realmente limpia. Personalmente, crearía una instancia de PathVisitor por llamada de método en lugar de usar el bloqueo, pero los documentos no dicen nada acerca de las recomendaciones de ninguna manera o si es un objeto pesado de crear. No tiene un Dispose(), por lo que me indica que no contiene muchos recursos. – angularsen

+0

Agregué una versión revisada sin bloqueo, moví los parámetros genéricos a la clase por lo que solo necesita especificar TSource y un método práctico que devuelve una cadena con un separador de cadenas configurable: https://gist.github.com/anjdreas/862c1cd9983d7525d2ddee0bb2706c3a – angularsen

+0

Sí , el bloqueo es tonto allí, actualizando la respuesta. –

Cuestiones relacionadas