2009-02-19 8 views
10

Estaba buscando usar una expresión lamba para permitir que los eventos se cableen de una manera fuertemente tipada, pero con un oyente en el medio, p. teniendo en cuenta las siguientes clasesEventos en expresiones lambda - ¿Error del compilador C#?

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 se cablea como:

Listener.WireUp<Producer, Consumer>((p, c) => p.MyEvent += c.MyHandler); 

Sin embargo, esto da un error del compilador:

CS0832: An expression tree may not contain an assignment operator

Ahora al principio esto parece razonable, sobre todo después de reading the explanation about why expression trees cannot contain assignments . Sin embargo, a pesar de la sintaxis de C#, la += no es una asignación, se trata de una llamada al método Producer::add_MyEvent, como podemos ver en la CIL que se produce si sólo cablear el evento hasta 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) 

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 del compilador?". Lo siento si no tenía claro lo que estaba preguntando.

Editar 2

Después de leer la respuesta Inferis', donde dice que 'en ese momento el + = se considera que la asignación' esto hace que tenga algún sentido, porque en este momento el compilador no lo hace podría decirse Sé que se convertirá en CIL.

Sin embargo no se me permite escribir la forma llamada de método explícito:

Listener.WireUp<Producer, Consumer>(
    (p, c) => p.add_MyEvent(new EventHandler(c.MyHandler))); 

Da:

CS0571: 'Producer.MyEvent.add': cannot explicitly call operator or accessor

tanto, creo que la cuestión se reduce a lo += realmente significa en el contexto de C# eventos. 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, entonces no es intuitivo, pero podría decirse que no es un error. ¿Pensamientos?

+1

+ = significa "llamar al acceso del evento para el evento dado". Creo que no es un error como tal, sino una restricción ligeramente molesta y posiblemente innecesaria. –

Respuesta

5

En la especificación, la sección 7.16.3, el + = y - = operadores se llaman "asignación de eventos" que sin duda 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 que es demasiado restrictivo ya que es perfectamente posible que un árbol de expresiones represente la funcionalidad dada por la expresión lambda.

I sospechoso los diseñadores de idiomas optaron por el enfoque "un poco más restrictivo pero más consistente en la descripción del operador", a expensas de situaciones como esta, me temo.

+0

Jon - sí, ahí es donde estaba llegando (ver mi edición n. ° 2). Así que creo que lo que la especificación indica es que + = con eventos significa "asignar al evento de una manera aún no definida" en lugar de "llamar al método add para mí". En ese caso, sería frustrante, pero técnicamente no es un error. –

+0

No creo que sea "aún indefinido": podría identificar el método y emitir un árbol de expresiones que así lo llame. Ese sería un comportamiento razonable, excepto que estaría entregando un operador que sigue siendo, estrictamente hablando, un operador de asignación. –

+0

OK, me has convencido; Creo que técnicamente es un operador de asignación, lo que significa que no es un error. Pero es frustrante, ya que todos * sabemos * en realidad es una llamada al método debajo ... ah bien: -S –

1

+ = es una tarea, independientemente de lo que haga (por ejemplo, agregar un evento). Desde el punto de vista del analizador, sigue siendo una tarea.

¿Usted intentó

Listener.WireUp<Producer, Consumer>((p, c) => { p.MyEvent += c.MyHandler; }); 
+0

El analizador puede verlo como una tarea, pero como dije, semánticamente y físicamente no es una tarea, es una llamada a un método. Eso es lo que quise decir al decir que parece ser un error del compilador. El compilador debe saber que en este contexto, no es una tarea. –

+0

Además, poner corchetes alrededor de la expresión significa que ya no es un lambda, es un delegado simple, lo que significa que no se puede convertir a un árbol de expresiones. –

+0

No entendí el concepto de toda la construcción del oyente en primer lugar:/ – Timbo

1

¿Por qué desea utilizar la clase de expresión? Cambie Expression<Action<TProducer, TConsumer>> en su código a simplemente Action<TProducer, TConsumer> y todos deberían 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.

Edit: Bien, entiendo su 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.

+0

Porque necesito acceder al árbol de expresiones para poder capturar qué evento y qué método de manejo debe estar conectado. Sin el árbol de expresiones no puedes hacer esto. –

+0

Bastante justo. Mi publicación ahora se actualiza para proporcionar una solución alternativa. – Noldorin

+1

Desafortunadamente, no parece haber ninguna forma fuertemente tipada para obtener p.MyEvent - tratando de usarlo sin + = resulta en un error CS0070: El evento 'Producer.MyEvent' solo puede aparecer en el lado izquierdo de + = o - = (excepto cuando se usa dentro del tipo 'Productor') –

0

Creo que el problema es, que aparte del objeto Expression<TDelegate>, el árbol de expresión no es de tipo estático desde la perspectiva del compilador. MethodCallExpression y sus amigos no exponen información de escritura estática.

A pesar de que el compilador sabe todos los tipos en la expresión, esta información se tira al convertir la expresión lambda a un árbol de expresión. (Para consultar el código para generar árboles de expresión)

Me gustaría, sin embargo, la posibilidad de presentar esto a Microsoft.

1

En realidad, en lo que respecta al compilador en ese punto, es una asignación. 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.

Así que lo que hace el compilador es decir: crea una expresión en donde añades 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 desea que el método WireUp para tomar una expresión y no sólo una acción?

+0

Porque necesito acceder al árbol de expresiones para poder capturar qué evento y qué método de manejo debe estar conectado. Sin el árbol de expresiones no puedes hacer esto. –

+0

Pero, veo lo que quieres decir, creo que la parte clave de esta respuesta es "en ese punto" ... déjame editar la pregunta. –