2011-11-26 12 views
22

Supongamos que tengo una clase:¿Por qué los argumentos de expresión lambda son ambiguos entre Func y Expresión <Func>?

class MyClass { 
    public int MyMethod(Func<int, int> f) { return 0; } 
    public int MyMethod(Expression<Func<int, int>> f) { return 1; } 
} 

Cuando intento llamar al método con una expresión lambda, me sale un error de compilación que indica que la llamada es ambigua entre las dos sobrecargas:

var myClass = new MyClass(); 
myClass.MyMethod(x => 1 + x); // Error! 

mientras que, por supuesto, llamar con un tipo explícito funciona bien:

myClass.MyMethod((Func<int, int>)(x => 1 + x)); // OK, returns 0 
myClass.MyMethod((Expression<Func<int, int>>)(x => 1 + x)); // OK, returns 1 

el árbol de expresión contiene más información (el código real), y puede wa nt para hacer uso de esta información cuando esté disponible. Pero también quiero que mi código funcione con los delegados. Desafortunadamente, esta ambigüedad lo hace así que tengo que encontrar otra manera de distinguir entre las dos llamadas, lo que arruina una API que de otro modo estaría limpia.

La especificación de C# no dice nada acerca de esta situación específica, por lo que en este sentido el comportamiento coincide con la especificación.

Sin embargo, hay un argumento para hacer que el árbol de expresiones se prefiera sobre el delegado. El método Compile actúa como una conversión explícita de un árbol de expresiones a un delegado. El árbol de expresiones contiene más información, y cuando compila a un delegado, pierde esa información. No hay conversión en la otra dirección.

¿Hay alguna razón para no preferir el árbol de expresiones?

+3

El argumento a favor de 'Func' es que puede ejecutarlo directamente sin tener que pasar por una etapa de compilación relativamente costosa. – Lee

Respuesta

6

Respondiendo a la pregunta sobre el título, son ambiguos porque el sistema de tipo no tiene ningún concepto de "expresión lambda". Es una característica del compilador que se puede convertir en un delegado o un árbol de expresiones, por lo que debe ser explícito con respecto a qué tipo desea convertirlo. La mayoría de las veces, el compilador también invoca automáticamente el objetivo debido al contexto en el que se utiliza la expresión lambda. Por ejemplo, el uso de lambdas en los métodos de extensión IEnumerable versus el uso de lambdas en los métodos de extensión IQueryable.

Ahora, para responder a la pregunta sobre por qué no siempre prefiere el árbol de expresiones, tiene el argumento de rendimiento que MagnatLU ya indicó.Si acepta un Expression y luego llama al Compile para poder ejecutarlo, siempre será más lento en comparación con la aceptación de un delegado.

También hay una diferencia semántica entre los dos, un delegado es solo una forma de ejecutar código mientras que un árbol de expresiones es una descripción del código real.

Si yo fuera usted, optaría por cambiar el nombre del método aceptando la expresión a algo que refleje claramente lo que hace extra a la basada en el delegado.

+1

El hecho de que una expresión lambda pueda interpretarse de dos maneras no significa en sí mismo que no podría haber una regla para preferir una sobre la otra. Entonces realmente no me gusta esa parte de tu respuesta. El resto es genial, sin embargo. ¡Gracias! –

0

Construir y compilar árbol de expresiones lleva tiempo y puede ejercer una gran presión sobre GC. No deberías construir y compilar expresiones en tiempo de ejecución a menos que realmente tengas que hacerlo.

Editar: Además, tenga en cuenta que no todas las expresiones o métodos se pueden expresar como Expression<E>. Func<U, V> solo se preocupa por los parámetros y el tipo de devolución y no tiene tales limitaciones.

2

¿Cómo sabrá el compilador si elegir el árbol de expresión o delegar? Es su decisión y no del compilador.

Todos los métodos de linq también proporcionan delegados y expresiones, pero son extensiones en forma de tipo de destino.

Enumerable.Where<T>(this IEnumerable<T> en , Func<T,bool> filter) 
Queryable.Where<T>(this IQueryable<T> en , Expression<Func<T,bool>> filter) 

Basado en el tipo de compilador de tipo de destino. El compilador es un programa, de hecho es una máquina y no tiene contexto, no puede tomar una decisión como humana sobre lo que podría ser mejor, solo sigue las reglas y esas reglas deben ser inequívocas.

+1

La pregunta no es por qué el compilador lo etiqueta como ambiguo: eso es porque está en la especificación por omisión. La pregunta es por qué los diseñadores del lenguaje lo dejaron ambiguo. –

+0

Por qué no tiene una respuesta específica ya que no es posible diseñar todas las permutaciones y combinaciones, de esta manera habrá muchas reglas de este tipo que son de baja prioridad y escenarios de uso poco comunes. No es práctico adivinar y agregar todas las reglas de diseño. –

Cuestiones relacionadas