expressions create .net lambda expression-trees

.net - create - ¿Por qué los argumentos de expresión lambda son ambiguos entre Func y Expression<Func>?



lambda c# (3)

Supongamos que tengo una clase:

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

Cuando trato de llamar al método con una expresión lambda, aparece 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 expresiones contiene más información (el código real), y es posible que desee hacer uso de esta información cuando esté disponible. Pero también quiero que mi código funcione con delegados. Desafortunadamente, esta ambigüedad lo hace así que tengo que encontrar otra forma de distinguir entre las dos llamadas, lo que desordena una API por lo demás limpia.

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

Sin embargo, hay un argumento que se debe hacer para que el árbol de expresiones se prefiera sobre el delegado. El método de 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 compilas a un delegado, pierdes esa información. No hay conversión en la otra dirección.

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


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

Todos los métodos linq proporcionan tanto delegado como 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)

Así que basado en el compilador de tipo de destino hace la elección. El compilador es un programa, de hecho es una máquina y está libre de contexto, no puede tomar decisiones como humano sobre lo que podría ser mejor, solo sigue las reglas y esas reglas deben ser inequívocas.


Construir y compilar el árbol de expresiones lleva tiempo y puede ejercer una presión significativa sobre GC. No debe construir y compilar expresiones en tiempo de ejecución a menos que realmente tenga que hacerlo.

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


Respondiendo a la pregunta del título, son ambiguos porque el sistema de tipos no tiene el concepto de "expresión lambda". Es una característica del compilador que se puede convertir en un delegado o en un árbol de expresiones, por lo que debe ser explícito a qué tipo desea convertirlo. La mayoría de las veces, el compilador también deduce 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 frente al uso de lambdas en los métodos de extensión IQueryable .

Ahora, para responder a la pregunta de por qué no siempre se prefiere el árbol de expresiones, tiene el argumento de rendimiento que MagnatLU ya declaró. Si acepta una Expression y luego llama a Compile para poder ejecutarla, entonces 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 un código, mientras que un árbol de expresiones es una descripción del código real.

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