español - Eventos en expresiones lambda-¿Error del compilador C#?
func c# lambda (5)
¿Por qué quieres usar la clase Expression? Cambie Expression<Action<TProducer, TConsumer>>
en su código a simplemente Action<TProducer, TConsumer>
y todo debería funcionar como lo desee. Lo que estás haciendo aquí es forzar al compilador a tratar la expresión lambda como un árbol de expresiones en lugar de un delegado, y un árbol de expresiones no puede contener tales asignaciones (se trata como una tarea porque estás usando el operador + = creo ) Ahora, una expresión lambda se puede convertir en cualquier forma (como se indica en [MSDN] [1]). Simplemente usando un delegado (eso es toda la clase de Acción), tales "asignaciones" son perfectamente válidas. Puede que haya entendido mal el problema aquí (tal vez hay una razón específica por la que necesita usar un árbol de expresiones), pero parece que la solución es afortunadamente así de simple.
Editar: Bien, entiendo tu problema un poco mejor ahora por el comentario. ¿Hay alguna razón por la que no pueda simplemente pasar p.MyEvent y c.MyHandler como argumentos al método WireUp y adjuntar el controlador de eventos dentro del método WireUp (para mí esto también parece mejor desde un punto de vista de diseño) ... lo haría que no elimina la necesidad de un árbol de expresión? Creo que es mejor si evitas árboles de expresión de todos modos, ya que tienden a ser bastante lentos en comparación con los delegados.
Estaba buscando usar una expresión lamba para permitir que los eventos estén conectados de una manera fuertemente tipada, pero con un oyente en el medio, por ejemplo, dadas las siguientes clases
class Producer
{
public event EventHandler MyEvent;
}
class Consumer
{
public void MyHandler(object sender, EventArgs e) { /* ... */ }
}
class Listener
{
public static void WireUp<TProducer, TConsumer>(
Expression<Action<TProducer, TConsumer>> expr) { /* ... */ }
}
Un evento estaría conectado como:
Listener.WireUp<Producer, Consumer>((p, c) => p.MyEvent += c.MyHandler);
Sin embargo, esto da un error de compilación:
CS0832: un árbol de expresiones no puede contener un operador de asignación
Ahora, al principio esto parece razonable, particularmente después de leer la explicación sobre por qué los árboles de expresión no pueden contener asignaciones . Sin embargo, a pesar de la sintaxis de C #, el +=
no es una tarea, es una llamada al método Producer::add_MyEvent
, como podemos ver en el CIL que se produce si simplemente cableamos el evento normalmente:
L_0001: newobj instance void LambdaEvents.Producer::.ctor()
L_0007: newobj instance void LambdaEvents.Consumer::.ctor()
L_000f: ldftn instance void LambdaEvents.Consumer::MyHandler(object, class [mscorlib]System.EventArgs)
L_0015: newobj instance void [mscorlib]System.EventHandler::.ctor(object, native int)
L_001a: callvirt instance void LambdaEvents.Producer::add_MyEvent(class [mscorlib]System.EventHandler)
Así que me parece que esto es un error del compilador, ya que se queja de que no se permiten las asignaciones, pero no hay ninguna asignación, solo una llamada a un método. O me estoy perdiendo algo...?
Editar:
Tenga en cuenta que la pregunta es "¿Es este comportamiento un error de compilación?". Lo siento si no tenía claro lo que estaba preguntando.
Editar 2
Después de leer la respuesta de Inferis, donde dice "en ese momento el + = se considera asignación", tiene sentido, porque en este punto el compilador posiblemente no sepa que se convertirá en CIL.
Sin embargo, no estoy autorizado a escribir el formulario de llamada de método explícito:
Listener.WireUp<Producer, Consumer>(
(p, c) => p.add_MyEvent(new EventHandler(c.MyHandler)));
Da:
CS0571: ''Producer.MyEvent.add'': no se puede llamar explícitamente al operador o al programa de acceso
Entonces, supongo que la pregunta se reduce a lo que +=
realmente significa en el contexto de los eventos de C #. Significa "llamar al método de agregar para este evento" o significa "agregar a este evento de una manera aún no definida". Si es el primero, entonces me parece que es un error del compilador, mientras que si es el último, no es intuitivo, pero podría decirse que no es un error. ¿Pensamientos?
+ = es una tarea, sin importar lo que haga (por ejemplo, agregar un evento). Desde el punto de vista del analizador, sigue siendo una tarea.
Has probado
Listener.WireUp<Producer, Consumer>((p, c) => { p.MyEvent += c.MyHandler; } );
Creo que el problema es que, aparte del objeto Expression<TDelegate>
, el árbol de expresiones no está tipado estáticamente desde la perspectiva del compilador. MethodCallExpression
y sus amigos no exponen información de tipeo estático.
Aunque el compilador conoce todos los tipos de la expresión, esta información se descarta al convertir la expresión lambda a un árbol de expresiones. (Eche un vistazo al código generado para los árboles de expresión)
Sin embargo, consideraría enviar esto a microsoft.
En la especificación, sección 7.16.3, los operadores + = y - = se llaman "Asignación de evento", lo que ciertamente hace que suene como un operador de asignación. El hecho de que esté dentro de la sección 7.16 ("Operadores de asignación") es una gran pista :) Desde ese punto de vista, el error del compilador tiene sentido.
Sin embargo, estoy de acuerdo en que es demasiado restrictivo ya que es perfectamente posible que un árbol de expresiones represente la funcionalidad dada por la expresión lambda.
Sospecho que los diseñadores de lenguaje aceptaron el enfoque "un poco más restrictivo pero más coherente en la descripción del operador", a expensas de situaciones como esta, me temo.
En realidad, en lo que respecta al compilador en ese punto, es una tarea. El operador + = está sobrecargado, pero al compilador no le importa eso en su punto. Después de todo, estás generando una expresión a través del lambda (que, en un momento se compilará con el código real) y sin código real.
Entonces, lo que hace el compilador es decir: crea una expresión en la que agregas c.MyHandler
al valor actual de p.MyEvent
y almacena el valor cambiado nuevamente en p.MyEvent
. Y entonces realmente estás haciendo una tarea, incluso si al final no lo haces.
¿Hay alguna razón por la que desee que el método WireUp tome una expresión y no solo una Acción?