2012-06-23 9 views
8

tengo un método que quiero convertir a Método de extensiónMensaje de error "Operador". no se puede aplicar al operando de tipo 'lambda expression' "cuando se convierte un método a Método de extensión?

public static string GetMemberName<T>(Expression<Func<T>> item) 
{ 
    return ((MemberExpression)item.Body).Member.Name; 
} 

y decir que es como

string str = myclass.GetMemberName(() => new Foo().Bar); 

por lo que se evalúa como str = "Bar"; // It gives the Member name and not its value

Ahora cuando intento convertir esto en extensión método por este

public static string GetMemberName<T>(this Expression<Func<T>> item) 
{ 
    return ((MemberExpression)item.Body).Member.Name; 
} 

y llamarlo como

string str = (() => new Foo().Bar).GetMemberName(); 

error dice Operator '.' cannot be applied to operand of type 'lambda expression'

¿Dónde estoy mal?

Respuesta

7

En realidad, hay dos cosas que aquí, en primer lugar, que pasa () => new Foo().Bar en el método que acepta Expression<Func<T>> trata el árbol de expresión especificado como Expression<Func<T>>, pero () => new Foo().Bar no es una Expression<Func<T>> por su propia cuenta .

En segundo lugar, para que su método de extensión acepte cualquier lambda (como el que está suministrando), debería usar el tipo que corresponde a cualquier árbol de expresiones. Pero, como ya habrás adivinado en base al mensaje ... to operand of type 'lambda expression' donde normalmente verías el nombre del tipo dentro de las comillas, las expresiones lambda son tratadas especialmente por el idioma, haciendo lo que estás intentando hacer, sin lanzar primero imposible.

La forma de invocar el método de extensión en forma método de extensión sería (en el caso de que Bar es de tipo string)

((Expression<Func<string>>)(() => new Foo().Bar)).GetMemberName()` 

que no parece como que sería todo lo deseable.

+0

Funcionó para mí .. –

+0

No estoy seguro de seguir su comentario, funcionó para usted sin primero convertir a la expresión específica 'Expresión > 'o este estilo de llamada es satisfactorio? – mlorbetske

+0

Su respuesta funcionó para mí '((Expresión >) (() => new Foo(). Bar)). GetMemberName()' –

6

¿Dónde estoy equivocado?

El compilador le dice exactamente lo que está mal: no puede usar . en una expresión lambda.

La expresión lambda no tiene ningún tipo particular, es convertible en el árbol de expresiones.

Un miembro de de acceso expresión (que es lo que estamos tratando de hacer) está disponible sólo en las formas

primary-expression . identifier type-argument-list(opt) 
predefined-type . identifier type-argument-list(opt) 
qualified-alias-member . identifier type-argument-list(opt) 

... y una expresión lambda no es una expresión primaria.

Curiosamente, este argumento no se espera por una expresión anónima método, pero para usted todavía no puede utilizar una expresión de acceso de miembro del mencionado, tampoco. La sección 7.6.4 de la especificación C# enumera cómo se vincula una expresión de acceso miembro, y la mayoría de las opciones están en "Si E es tipo predefinido o expresión primaria clasificada como un tipo" (que no se aplica a métodos anónimos) o "Si E es un acceso de propiedad, variable o valor, cuyo tipo es T" - pero un método anónimo es una función anónima, y ​​según la sección 7.15: "Una función anónima no no tiene un valor o tipo en sí mismo ".

EDITAR: Usted puede seguir utilizando métodos de extensión en árboles de expresiones, simplemente no puede usarlos directamente en expresiones lambda. Entonces esto funcionará:

Expression<Func<int>> expr =() => new Foo().Bar; 
string name = expr.GetMemberName(); 

... pero obviamente no es tan útil. (Lo mismo ocurre con un molde de acuerdo con la respuesta de mlorbetske.)

+0

Gracias, pero ¿Quiere esto decir que este método no se puede convertir en EM? –

+0

@NikhilAgrawal: Bueno, puedes hacerlo, pero no en un solo paso. –

+0

o como @mlorbetske responde '((Expresión >) (() => new Foo(). Bar)). GetMemberName();' –

2

Para obtener la expresión escrita, tendrá que escribirla. Como han dicho otros, no hay forma de que el compilador lo infiera automáticamente de una expresión lambda, ya que una expresión lambda puede significar dos cosas: un delegado o un árbol de expresiones.

Usted puede obtener la expresión relativamente simple, al permitir que el compilador inferir el tipo para usted en parte, al igual que (from this answer):

public sealed class Lambda 
{ 
    public static Func<T> Func<T>(Func<T> func) 
    { 
     return func; 
    } 

    public static Expression<Func<T>> Expression<T>(Expression<Func<T>> expression) 
    { 
     return expression; 
    } 
} 

public sealed class Lambda<S> 
{ 
    public static Func<S, T> Func<T>(Func<S, T> func) 
    { 
     return func; 
    } 

    public static Expression<Func<S, T>> Expression<T>(Expression<Func<S, T>> expression) 
    { 
     return expression; 
    } 
} 

//etc, to cover more cases 

llamar así:

var expr1 = Lambda.Expression(() => new Foo().Bar); 
var expr2 = Lambda<string>.Expression(x => x.Length); //etc 

Su las opciones son:

  1. moldeada hacia atrás, para exigir tipo de expresión y luego llamar al método de extensión en él

    var name = ((Expression<Func<BarType>>)(() => new Foo().Bar)).GetMemberName(); 
    

    ve feo - remedio peor que causa.

  2. mostrar la expresión primero en una variable

    Expression<Func<BarType>> expr =() => new Foo().Bar; 
    var name = expr.GetMemberName(); 
    

    Un poco mejor, pero todavía se ve poco fuera de una cosa trivial.

  3. Usando los Lambda clases escritas por encima de

    var name = Lambda.Expression(() => new Foo().Bar).GetMemberName(); 
    

    aún mejor. Es solo un poco menos tipeo.

  4. Su primer patrón, que creo que es lo mejor que puede llegar tan lejos como la legibilidad va

    yo no creo que se pueda mejorar en este aspecto teniendo en cuenta las reglas de C# relacionados con las expresiones lambda. Dicho esto, creo que se pueden hacer pocas mejoras.

    Primero, extienda la funcionalidad a otros tipos de expresiones lambda. Necesitará más de Func<T> tipos para manejar todos los casos. Decida cuáles son los tipos de expresiones que debe manejar. Por ejemplo, si tiene un tipo Func<S, T> (como en su pregunta - Bar en Foo), se ve mejor.Compare esto

    myclass.GetMemberName(() => new Foo().Bar); 
    

    con

    myclass.GetMemberName<Foo>(x => x.Bar); 
    

    yo diría que estas sobrecargas haría:

    //for static methods which return void 
    public static string GetMemberName(Expression<Action> expr); 
    
    //for static methods which return non-void and properties and fields 
    public static string GetMemberName<T>(Expression<Func<T>> expr); 
    
    //for instance methods which return void 
    public static string GetMemberName<T>(Expression<Action<T>> expr); 
    
    //for instance methods which return non-void and properties and fields 
    public static string GetMemberName<S, T>(Expression<Func<S, T>> expr); 
    

    Ahora bien, estos pueden ser utilizados no sólo en los casos mencionados en los comentarios, seguramente hay superposición escenarios. Por ejemplo, si ya tiene una instancia de Foo, entonces es más fácil llamar a la sobrecarga de la segunda sobrecarga (Func<T>) para el nombre de la propiedad Bar, como myclass.GetMemberName(() => foo.Bar).

    En segundo lugar, implemente una funcionalidad GetMemberName común a todas estas sobrecargas. Un método de extensión en Expression<T> o LambdaExpression haría. Prefiero lo último para poder llamarlo incluso en escenarios sin tipeo fuerte. Lo escribiría así, from this answer:

    public static string GetMemberName(this LambdaExpression memberSelector) 
    { 
        Func<Expression, string> nameSelector = null; 
        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); 
    } 
    

    Por último, GetMemberName no es un buen nombre si estás llamarlo manera no extensión. expression.GetMemberName() suena más lógico. Member.NameFrom<int>(x => x.ToString()) o MemberName.From<string>(x => x.Length) etc. son nombres más descriptivos para llamadas estáticas.

    Así que en general la clase podría ser:

    public static class Member 
    { 
        public static string NameFrom(Expression<Action> expr) 
        { 
         return expr.GetMemberName(); 
        } 
    
        public static string NameFrom<T>(Expression<Func<T>> expr) 
        { 
         return expr.GetMemberName(); 
        } 
    
        public static string NameFrom<T>(Expression<Action<T>> expr) 
        { 
         return expr.GetMemberName(); 
        } 
    
        public static string NameFrom<T>(Expression<Func<T, object>> expr) 
        { 
         return expr.GetMemberName(); 
        } 
    } 
    

    y uso:

    var name1 = Member.NameFrom(() => Console.WriteLine()); 
    var name2 = Member.NameFrom(() => Environment.ExitCode); 
    var name3 = Member.NameFrom<Control>(x => x.Invoke(null)); 
    var name4 = Member.NameFrom<string>(x => x.Length); 
    

    más concisa y limpia.

  5. Para las propiedades y campos, que se puede transformar en una clase anónima y luego utilizando la reflexión el nombre del miembro se puede leer, as shown here.

    public static string GetMemberName<T>(T item) where T : class 
    { 
        if (item == null) 
         return null; 
    
        return typeof(T).GetProperties()[0].Name; 
    } 
    

    llamar así

    var name = GetMemberName(new { new Foo().Bar }); 
    

    Es más rápido, pero tiene ciertos caprichos, como no muy amigable con los refactores, y no ayuda en el caso de los métodos como miembros. Ver el hilo ..

De todos Prefiero 4.

Cuestiones relacionadas