I was looking at using a lamba expression to allow events to be wired up in a strongly typed manner, but with a listener in the middle, e.g. given the following classes
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) { /* ... */ }
}
An event would be wired up as:
Listener.WireUp<Producer, Consumer>((p, c) => p.MyEvent += c.MyHandler);
However this gives a compiler error:
CS0832: An expression tree may not contain an assignment operator
Now at first this seems reasonable, particularly after reading the explanation about why expression trees cannot contain assignments. However, in spite of the C# syntax, the +=
is not an assignment, it is a call to the Producer::add_MyEvent
method, as we can see from the CIL that is produced if we just wire the event up normally:
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)
So it looks to me like this is a compiler bug as it's complaining about assignments not being allowed, but there is no assignment taking place, just a method call. Or am I missing something...?
Edit:
Please note that the question is "Is this behaviour a compiler bug?". Sorry if I wasn't clear about what I was asking.
Edit 2
After reading Inferis' answer, where he says "at that point the += is considered to be assignment" this does make some sense, because at this point the compiler arguably doesn't know that it's going to be turned into CIL.
However I am not permitted to write the explicit method call form:
Listener.WireUp<Producer, Consumer>(
(p, c) => p.add_MyEvent(new EventHandler(c.MyHandler)));
Gives:
CS0571: 'Producer.MyEvent.add': cannot explicitly call operator or accessor
So, I guess the question comes down to what +=
actually means in the context of C# events. Does it mean "call the add method for this event" or does it mean "add to this event in an as-yet undefined manner". If it's the former then this appears to me to be a compiler bug, whereas if it's the latter then it's somewhat unintuitive but arguably not a bug. Thoughts?
No, C has no support for lambda expressions.
A lambda in Java essentially consists of three parts: a parenthesized set of parameters, an arrow, and then a body, which can either be a single expression or a block of Java code.
Events are user actions such as key press, clicks, mouse movements, etc., or some occurrence such as system generated notifications. Applications need to respond to events when they occur. For example, interrupts.
The => token is supported in two forms: as the lambda operator and as a separator of a member name and the member implementation in an expression body definition.
In the spec, section 7.16.3, the += and -= operators are called "Event assignment" which certainly makes it sound like an assignment operator. The very fact that it's within section 7.16 ("Assignment operators") is a pretty big hint :) From that point of view, the compiler error makes sense.
However, I agree that it is overly restrictive as it's perfectly possible for an expression tree to represent the functionality given by the lambda expression.
I suspect the language designers went for the "slightly more restrictive but more consistent in operator description" approach, at the expense of situations like this, I'm afraid.
+= is an assignment, no matter what it does (e.g. add an event). From the parser point of view, it is still an assignment.
Did you try
Listener.WireUp<Producer, Consumer>((p, c) => { p.MyEvent += c.MyHandler; } );
Actually, as far as the compiler is concerned at that point, it is an assignment. The += operator is overloaded, but the compiler doesn't care about that at it's point. After all, you're generating an expression through the lambda (which, at one point will be compiled to actual code) and no real code.
So what the compiler does is say: create an expression in where you add c.MyHandler
to the current value of p.MyEvent
and store the changed value back into p.MyEvent
. And so you're actually doing an assignment, even if in the end you aren't.
Is there a reason you want the WireUp method to take an expression and not just an Action?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With