views:

1706

answers:

5

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?

+1  A: 

+= 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; } );
Timbo
The parser may see it as an assignment, but as I stated, semantically and physically it is not an assignment, it is a method call. That's kind of what I meant by saying that it appears to be a compiler bug. The compiler should know that in this context, it is not an assignment.
Greg Beech
Also, putting curly brackets around the expression means it is no longer a lambda, it is a plain delegate, which means it cannot be converted to an expression tree.
Greg Beech
I didn't get the point of the whole listener construct in the first place :/
Timbo
How is that WireUp method implemented?
Timbo
@Greg: I agree for the most part, but not with your claim that "putting curly brakcets around the expression means it is no longer a lambda". It's still a lambda expression, but with a block body.
Jon Skeet
@Jon - I agree it's still written as a lambda, but in the functional programming sense, it isn't really one any more.
Greg Beech
If you're going to use a term which has a defined meaning in C#, and use it in a different sense, it's worth being explicit about that :) Bear in mind that in a future version of C# we may have the ability to convert lambdas with bodies into expression trees too :)
Jon Skeet
Fair enough, I can see your point. Technically I believe C# has the wrong terminology here, but given that we're talking within its context I should have clarified what I meant.
Greg Beech
+1  A: 

Why do you want to use the Expression class? Change Expression<Action<TProducer, TConsumer>> in your code to simply Action<TProducer, TConsumer> and all should work as you want. What you're doing here is forcing the compiler to treat the lambda expression as an expression tree rather than a delegate, and an expression tree indeed cannot contain such assignments (it's treated as an assignment because you're using the += operator I believe). Now, a lambda expression can be converted into either form (as stated on [MSDN][1]). By simply using a delegate (that's all the Action class is), such "assignments" are perfectly valid. I may have misunderstood the problem here (perhaps there is a specific reason why you need to use an expression tree?), but it does seem like the solution is fortunately this simple!

Edit: Right, I understand your problem a bit better now from the comment. Is there any reason you can't just pass p.MyEvent and c.MyHandler as arguments to the WireUp method and attach the event handler within the WireUp method (to me this also seems better from a design point of view)... would that not eliminate the need for an expression tree? I think it's best if you avoid expression trees anyway, as they tend to be rather slow compared to delegates.

Noldorin
Because I need to access the expression tree to be able to grab which event and which handler method should be wired up. Without the expression tree you can't do this.
Greg Beech
Fair enough. My post is now updated to provide an alternate solution.
Noldorin
Unfortunately, there doesn't seem to be any strongly-typed way to get hold of p.MyEvent - trying to use it without += results in error CS0070: The event 'Producer.MyEvent' can only appear on the left hand side of += or -= (except when used from within the type 'Producer')
Greg Beech
A: 

I think the problem is, that apart from the Expression<TDelegate> object, the expression tree is not statically typed from the perspective of the compiler. MethodCallExpression and friends do not expose static typing information.

Even though the compiler knows all the types in the expression, this information is thrown away when converting the lambda expression to an expression tree. (Have a look at the code generate for expression trees)

I would nonetheless consider submitting this to microsoft.

SealedSun
+1  A: 

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?

Inferis
Because I need to access the expression tree to be able to grab which event and which handler method should be wired up. Without the expression tree you can't do this.
Greg Beech
But, I see what you mean, I think the key part of this answer is "at that point"... let me edit the question.
Greg Beech
+3  A: 

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.

Jon Skeet
Jon - yes that's where I was just getting to (see my edit #2). So I think what the spec is indicating is that += with events means "assign to the event in an as-yet undefined manner" rather than "call the add method for me". In which case it would be frustrating, but technically not a bug.
Greg Beech
I don't think it's even "as yet undefined" - it could identify the method and emit an expression tree which calls that. That would be reasonable behaviour, except that it would be handing an operator which is still - strictly speaking - an assignment operator.
Jon Skeet
OK you've convinced me; I think it is technically an assignment operator, which means this isn't a bug. But it's frustrating as we all *know* it's really a method call underneath... ah well :-S
Greg Beech
Could you not perhaps use System.MulticastDelegate.Combine to add an event handler? By the way, I still think the solution given in my edited post should do the job.
Noldorin
@Noldorin - Unfortunately it appears that any attempt to do anything clever (including calling Combine) results in error CS0070: The event 'Producer.MyEvent' can only appear on the left hand side of += or -= (except when used from within the type 'Producer')
Greg Beech
@Noldorin: No, you can't use Delegate.Combine - that's done within the add_EventHandler part, in the event (typically). Events are *just* add/remove methods.
Jon Skeet