Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Events in lambda expressions - C# compiler bug?

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?

like image 292
Greg Beech Avatar asked Feb 19 '09 11:02

Greg Beech


People also ask

Does C have lambda expression?

No, C has no support for lambda expressions.

What are the three parts of a lambda expression?

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.

What are events in C# with example?

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.

What is the use of => in C#?

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.


3 Answers

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.

like image 181
Jon Skeet Avatar answered Sep 30 '22 16:09

Jon Skeet


+= 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; } );
like image 33
Timbo Avatar answered Sep 30 '22 17:09

Timbo


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?

like image 31
Inferis Avatar answered Sep 30 '22 16:09

Inferis