Why can't I unsubscribe from an Event Using a Lambda Expression? - c#

This article states You Can’t Unsubscribe from an Event Using a Lambda Expression.
E.g. you can subscribe as follows:
d.Barked += (s, e) => Console.WriteLine("Bark: {0}", e);
but you can't unsubscribe like this:
d.Barked -= (s, e) => Console.WriteLine("Bark: {0}", e);
Why? What's the difference between this and unsubscribing from a delegate, e.g.
EventHandler<string> handler = (s, e) => Console.WriteLine("Bark: {0}", e);
d.Barked += handler;
// ...
d.Barked -= handler;

It all comes down to: when are two delegates considered the same for the purposes of delegate addition / subtraction. When you unsubscribe, it is essentially using the logic from Delegate.Remove, which considers two delegates equivalent if both the .Target and the .Method match (at least, for the simple case of a delegate with a single target method; multicast is more complicated to describe). So: what is the .Method and .Target on a lambda (assuming we are compiling it to a delegate, and not to an expression)?
The compiler actually has a lot of freedom here, but what happens is:
if the lambda includes a closure over a parameter or variable, the compiler creates a method (the method) on a compiler-generated class that represents the capture-context (which can also include the this token); the target is the reference to this capture-context instance (which will be defined by the capture scope)
if the lambda doesn't include a closure over a parameter or variable, but does make use of per-instance state via this (implicit or explicit), the compiler creates an instance method (the method) on the current type; the target is the current instance (this)
otherwise the compiler creates a static method (the method), and the target is null (incidentally, in this scenario it also includes a nifty field to cache a single static delegate instance - so in this scenario, only one delegate is ever created per lambda)
What it doesn't do, however, is compare lots of lambdas with similar looking bodies to reduce any. So what I get when I compile your code is two static methods:
[CompilerGenerated]
private static void <Main>b__0(object s, string e)
{
Console.WriteLine("Bark: {0}", e);
}
[CompilerGenerated]
private static void <Main>b__2(object s, string e)
{
Console.WriteLine("Bark: {0}", e);
}
(the Main here is just because in my test rig those lambdas are inside the Main method - but ultimately the compiler can choose any unpronounceable names it chooses here)
The first method is used by the first lambda; the second method is used by the second lambda. So ultimately, the reason it doesn't work is because the .Method doesn't match.
In regular C# terms, it would be like doing:
obj.SomeEvent += MethodOne;
obj.SomeEvent -= MethodTwo;
where MethodOne and MethodTwo have the same code inside them; it doesn't unsubscribe anything.
It might be nice if the compiler spotted this, but it is not required to, and as such it is safer that it doesn't elect to - it could mean that different compilers start producing very different results.
As a side note; it could be very confusing if it did try to de-dup, because you'd also have the issue of capture contexts - it would then be the case that it "worked" in some cases and not others - without being obvious which - probably the worst possible scenario.

Related

Subscribe to a delegate that was passed as method argument

I'm attempting to build a recurring programming pattern into a generic class for code re-use. Part of this pattern thread-safely subscribes/unsubscribes to a delegate as needed during asynchronous operations (multicast delegate used as event).
The following code does not compile. The problem I'm facing is that C# allows me to pass a delegate as method argument, but anywhere inside MyWorker class the subscribe +=,-= operations cause errors. (Works fine on the same delegate from outside).
[Operator '+=' cannot be applied to operands of type 'delegateT' and 'method group']
Syntactical errors?
delegate scope limitation?
is this disallowed by the language?
public class MyWorker<delegateT,argT>
{
private delegateT mDelegate;
public MyWorker(delegateT d)
{
mDelegate = d; //save delegate (reference?) for use later
}
public void DoWorkAsync()
{
mDelegate += m_subscriber; //ERROR: Operator '+=' cannot be applied to operands of type 'delegateT'...
//...Do some work that causes delegate to fire...
mDelegate -= m_subscriber; //ERROR: Operator '-=' cannot be applied to operands of type 'delegateT'...
}
private void m_subscriber(argT arg)
{
Console.WriteLine("Received: " + arg.ToString());
}
}
Note that generics do not seem to be the cause; I've tried using static types instead with the same result. I've also tried passing delegate with the 'ref' keyword to make sure i'm storing and referencing the original object rather than a local copy...maybe a secondary issue, but one step at a time.
[UPDATE]
OIC, inability to constrain Delegate type seems to be a major issue here, and prevents generic usage the way I'm doing it. Thanks for pointing that out Ron.
To clarify the original intent:
I use delegates for internal events; more flexibility than strict 'event' type. I'm searching for some way to pass a delegate (ref?) to MyWorker class at runtime. Then MyWorker performs some background tasks which subscribe and unsubscribe as necessary to receive events during operation, before finally exiting for garbage collection. I do this for 30+ nearly identical tasks/events, which is why creating a reusable (generic?) class is highly desirable.
Delegates in my system have a strict form such as:
delegate void delegateEvent1(Class1 arg);
delegate void delegateEvent2(Class2 arg);
...
delegate void delegateEventN(ClassN arg);
Plan B is to pass Action delegates to subscribe and unsubscribe from within MyWorker. This is quite a bit messier as it requires 2 Actions<> to be created for each invocation rather than just cleanly passing the desired delegate.
Open to any suggestions...
It's not because you named your generic parameter delegateT compiler knows it's some delegate type and so it can't know that some += operator exists on that type.
It would need some generic constraint to enforce this ; but AFAIK that's not possible in C#
As KiwiPiet pointed in comment you can maybe try to add a constraint for some delegate type though
As pointed by Ron Beyer in comment it's also not possible to constraint to some delegate type too

Delegates, Actions, Events, Lambda expression and MVVM

I have spent few days trying to understand WPF and MVVM. It is going very slowly mostly because I have some lack of knowledge in terms of events and stuff. Here bellow I will try to explain my understanding of all this things:
Method – this one is simple and I don't think it needs any explanation. Basic ingredient in any program.
Delegate – the way I see it is pointer on method. I can think of only few applications where I would want to use it over a method.
Action – that one is even trickier. Information I have managed to find say that it is a delegate that doesn't return value... so is it just pointer on void method? I don't see point of that
Event – this one I don't get at all. It was being explained with delegate and I didn't understand how does it work and what is it for. Note I was using events writing winforms applications but it was just choosing desired event from the list.
Event handler – even more unclear.
Lambda expression – also yet another way of using method. Again I understand it doesn't return anything, I can pass some argument in it, but still aint much different from void method. I have seen some applications like when using LINQ but I still don't understand how it works.
I would like to start by saying that I understand basic construct of MVVM, what is doing what and so on. Issue I have is that I don't understand some of the code, how does it work and therefore I can't write anything actually on my own. I will be using some tutorials as example so here it goes:
S1: https://msdn.microsoft.com/en-us/magazine/dd419663.aspx#id0090030
S2: http://social.technet.microsoft.com/wiki/contents/articles/18199.event-handling-in-an-mvvm-wpf-application.aspx
What I am expecting from you guys is some guidance or explanation how can I approach and understand those thinks to make them at least a little less scary for me. Here I will place some examples that will hopefully show you what kind of problems I have.
1) First one comes from S1 from well known RelayCommand class:
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
I know what it is suppose to do (name speaks for itself). But I don't understand how this thing works? How it knows when to make something executable and when not. What are exactly those add and remove “commands”? I tried to read about it but it didn't help.
2) Another example form S1:
#region CloseCommand
/// <summary>
/// Returns the command that, when invoked, attempts
/// to remove this workspace from the user interface.
/// </summary>
public ICommand CloseCommand
{
get
{
if (_closeCommand == null)
_closeCommand = new RelayCommand(param => this.OnRequestClose());
return _closeCommand;
}
}
#endregion // CloseCommand
#region RequestClose [event]
/// <summary>
/// Raised when this workspace should be removed from the UI.
/// </summary>
public event EventHandler RequestClose;
void OnRequestClose()
{
EventHandler handler = this.RequestClose;
if (handler != null)
handler(this, EventArgs.Empty);
}
#endregion // RequestClose [event]
Again I know what it is suppose to do, I even understand what is basically happening here but I don't see where this “thing” is actually doing something. OnRequestClose() is just creating handler that in my eyes doesn't do anything to close whatever it is suppose to close. Problem is that if I even don't see where command is executed how can I write my own commands.
3) I think this will be last example, this time from S2:
public ViewModel()
{
_clickCommand = new DelegateCommand<string>(
(s) => { /* perform some action */ }, //Execute
(s) => { return !string.IsNullOrEmpty(_input); } //CanExecute
);
}
Here problem is pretty simple. It is creating command using RelayCommand ctor (or at least it's version in this project, here called “DelegateCommand”). I don't understand those (s) and use of lambda. What is it for?
Of course that isn't everything I have problem with but I think that will give idea what is my problem to anyone willing to help. I tried to explain my problem as best as I can and I would really appreciate any help or guidance. Maybe I am expecting to much from myself but I feel like I need to know all that stuff in order to write anything serious.
Anyway thank you all in advance for any help.
A long answer for your broad question.
Let me dig into Events and EventHandler first using a simple approach
Suppose there is a big function organized in your city which will host many famous people from your country.
Let's consider three guest's for this example
Guest one is a person who is unknown by other guests
Guest two is a famous person from your area and very few people know
Guest three is very famous across your country
Consider the following assumptions
Nobody is waiting for guest one (0 EventHandler)
There are four
people who are waiting for guest two (4 EventHandler's each person is
an event handler waiting to greet)
There are three security personnel and 10 guests (out of which one
person is also waiting for guest 2) waiting for guest 3 (13
EventHandler's)
Scenario 1
When guest one arrives at the venue (Event raised) nothing happens
Scenario 2
When guest two arrives at the venue (Event raised) the four people move towards him/her and give greetings
Scenario 3
When guest three arrives (Event) you will see security forces providing cover and ten people move towards him/her and give greetings
Simple points to be observed
1. Event is just a notification that something has happened (like a delegate with a method signature)
2. EventHandler is an action as to what happens when a specific event has happened ( a method which implements the method signature defined
by the delegate)
3. One event can have many eventhandlers (e.g.. scenario 2,3). Hence the syntax += in the below code sample
The below code will answer some of your basic questions
class Program
{
static void Main(string[] args)
{
var test = new Example();
Console.ReadLine();
}
}
class Example
{
//This is the event definition
delegate void ActionDelegate(string input1, string input2);
// Two event handler which implements the signature of the event
void ActionMethod(string a, string b)
{
Console.WriteLine(a + " " + b);
}
void ActionMethod2(string c, string d)
{
Console.WriteLine("Wow one more function called with parameter {0} and {1}", c, d);
}
delegate Tuple<string, string> LamdaDelegate(string input1, string input2);
public Example()
{
//Did not declare any delegate member variable explicitly.
//Clean and easy to understand
Action<string, string> action = ActionMethod;
// Had to define the delegate with method signature explicitly before using it
ActionDelegate actionDelegate = ActionMethod;
actionDelegate += ActionMethod2; // Attaching more event handlers to the event
//The below lambda expression implicitly means that it will take two inputs each of type string
// and does not return anything.
//The type information is implicitly derived from the method signature of the delegate
actionDelegate += (a, b) => Console.WriteLine("Called using lambda expression");
//Below is a Lambda expression in which s and e each is of type string
//Since the return type of the delegate is Tuple<string,string> the same is returned by the expression
//Lambda expression is using a delegate without defining a delegate explicitly.
LamdaDelegate myTuple = (s, e) => { return Tuple.Create(s, e); };
//The above Lambda can be rewritten as
myTuple += delegate (string a, string b) { return Tuple.Create(a, b); };
//Invoking the event handlers. The event handlers are executed automatically when ever the event occurs
action("Hi", "called from action");
actionDelegate("Hi", "called using explicitly defined delegate");
}
}
Why should we use delegates?
In the above example you saw that the ActionMethod is used by two different delegates(read Event).
Without delegates the above example would be dirty and unreadable.
The above example also shows Action which simplifies the need to define delegates explicitly.
Now comming to Commands
In MVVM the traditional event is replaced by Command and the event parameters (read delegate method signature) is replaced by CommandParameter
In your 3rd question the DelegateCommand has a type parameter which is string. So the (s) you see is a variable which stores the input string sent from UI. Now it is upto you to decide if you want to use the input (s) or ignore it all together. In the CanExecute part you can see that even when the input (s) is passed it has ignored it and uses the other member variable _input.
The 2nd question an event RequestClose is defined which is attached an event handler in Figure 7 of the link. Also observe that in the same figure the MenuItem is bound to the CloseCommand in the ViewModel.
So now when you click on the MenuItem the CloseCommand is invoked and it then calls the function OnRequestClose. The first thing this function checks is that is there anyone interested(read listening) in the event RequestClose which the MainWindow is listening and hence calls the window.Close()
For more clarification please do let me know.
EDIT
The code example above was just for Action<T> and delegate.
In layman terms let me put it this way
If I want to do something based on some external action I would use Event . I will just define an event and wire it up to an EventHandler and just wait for the action to occur. (In the example above I don't know when will the guest arrive but whenever they do arrive the EventHandler will respond automatically)
On the other hand I will use delegate if I want to call single/multiple functions simultaneously based on some internal condition defined in the code. In the code example above you can see that I had to invoke the delegate manually.
Please ignore the Tuple as it is just a return value with no actual importance.
EDIT 2
The scenario (_) or (s) is similar. Both mean that you will get a parameter as an input but in the first scenario the developer is trying to say that this parameter will not be used or ignored while in the (s) they intend to use it
You can't use just () as this means that the lambda expression does not contain any input parameter which is wrong since the definition says that it will receive a string as an input.
1. Delegate
the way I see it is pointer on method
Correct. However since C# is strongly typed, you need to define, what parameters and return type must the method have. This is what delegates are.
I can think of only few applications where I would want to use it over
a method.
This is not about method vs deledate. When you need to pass method as a parameter, then the parameter is defined using delegate.
2. Action
Action is concrete type of delegate. While delegate can refer to any method, Action says, that it is parameterless method with return type void. Action<string> means that parameter is of type string. Action<string, int> means that the method's first parameter must be of type string and second of type int.
Another concrete example of delegate is Func delegate. Func<bool> means that the method does not have parameters and returns bool. Func<string, bool> means that there is one input parameter of type string and it returns bool.
EventHandler is just another delegate, that represents method with void return type and two input parameters: object and EventArgs
3. Event
just think about Button.Click event. The programmers at microsoft that worked on Button class had to provide a way how to notify your program, that button was clicked. So they defined an event and you can handle the event by providing a method that will execute when the event occurs. The method is called eventhandler.
4. EventHandler
is either used as common name for methods that are assinged to events or it is concrete delegate that defines input and output parameters or it is used as.
e.g: Button.Click += DoSomehing; Click is an event. Method DoSomething is and eventhandler. Is must return void and have two input parameters of type object and EventArgs, because when you look at the definition of click event, you can see concrete delegate type which is EventHandler (or RoutedEventHandler - just another delegate used in WPF in some events).
5. Lambda expression
is very simple. Lambda expression is just another syntax to write a method. Compiler translates lambdas to methods anyway. Lambdas are just syntactic sugar.
following method and lambda are equal
bool IsGreaterThan5(int number)
{
return number > 5;
}
var isGreaterThan5 = new Func<int, bool>(number => number > 5);
for example, if you have an array of integers, then you can use Where linq extention method to filter the array. Intelli sense tells you, that the parameter of Where method is Func<int, bool>. Therefore you need to pass method that takes one parameter of type int and returns bool.
int[] numbers = new []{ 1, 2, 3 4, 5, 6, 7 };
var filteredNumbers = numbers.Where(IsGreaterThan5); //method IsGreaterThan5 is defined in previus example
//just another syntax:
var filteredNumbers = numbers.Where(number => number > 5);
//just another syntax:
var filteredNumbers = numbers.Where(new Func<int, bool>(number => number > 5));
//just another syntax:
Func<int, bool> isGreaterThan5 = i => i > 5; //it does not matter how you name the input parameter
var filteredNumbers = numbers.Where(isGreaterThan5);

Delegate Subtractive Assignment

In C#, delegate behave more like a list of function pointers, so that we can call a list of methods with the same method signature via a delegate.
After the initial assignment, we can add or subtract methods from a delegate using += (additive assignment) and -= (subtractive assignment).
Say, I have two methods.
public static void Method1(string message){
// ...
}
public static void Method2(string message){
// ...
}
Now, I declare a delegate type,
public delegate void Del(string message);
If I want to execute Method1, Method2 and again Method1 in order, I can create a delegate like this.
Del delList = Method1;
delList += Method2;
delList += Method1;
Then, later in the program, If I remove Method1 from it, which occurrence of Method1 is removed? Is there any rule governing that?
I believe that the better and more readable way is to create a new delegate and add methods you want excute in order, instead of changing an existing one. I'm just curious about how -= operator is implemented for C# delegates.
If the matching performed by -= doesn't remove the one you wanted, you can call GetInvocationList(), manipulate it how you want, and make a new delegate.
However, that's only possible when you have access to the delegate. For fields, which only have operator+= and operator-= (subscribe and unsubscribe) behaviors, you'd best avoid duplicates if you care about the order of the calls.
Actually, it would probably be best to avoid duplicates altogether.
To answer the question about the specific behavior (though I still maintain that relying on it is far too confusing), operator-= uses Delegate.Remove, which is documented as:
Removes the last occurrence of the invocation list of a delegate from the invocation list of another delegate.
(The documentation for the Delegate class itself says "Managed languages use the Combine and Remove methods to implement delegate operations. Examples include the AddHandler and RemoveHandler statements in Visual Basic and the += and -= operators on delegate types in C#.")

Why are lambda expressions not "interned"?

Strings are reference types, but they are immutable. This allows for them to be interned by the compiler; everywhere the same string literal appears, the same object may be referenced.
Delegates are also immutable reference types. (Adding a method to a multicast delegate using the += operator constitutes assignment; that's not mutability.) And, like, strings, there is a "literal" way to represent a delegate in code, using a lambda expression, e.g.:
Func<int> func = () => 5;
The right-hand side of that statement is an expression whose type is Func<int>; but nowhere am I explicitly invoking the Func<int> constructor (nor is an implicit conversion happening). So I view this as essentially a literal. Am I mistaken about my definition of "literal" here?
Regardless, here's my question. If I have two variables for, say, the Func<int> type, and I assign identical lambda expressions to both:
Func<int> x = () => 5;
Func<int> y = () => 5;
...what's preventing the compiler from treating these as the same Func<int> object?
I ask because section 6.5.1 of the C# 4.0 language specification clearly states:
Conversions of semantically identical
anonymous functions with the same
(possibly empty) set of captured outer
variable instances to the same
delegate types are permitted (but not
required) to return the same delegate
instance. The term semantically
identical is used here to mean that
execution of the anonymous functions
will, in all cases, produce the same
effects given the same arguments.
This surprised me when I read it; if this behavior is explicitly allowed, I would have expected for it to be implemented. But it appears not to be. This has in fact gotten a lot of developers into trouble, esp. when lambda expressions have been used to attach event handlers successfully without being able to remove them. For example:
class EventSender
{
public event EventHandler Event;
public void Send()
{
EventHandler handler = this.Event;
if (handler != null) { handler(this, EventArgs.Empty); }
}
}
class Program
{
static string _message = "Hello, world!";
static void Main()
{
var sender = new EventSender();
sender.Event += (obj, args) => Console.WriteLine(_message);
sender.Send();
// Unless I'm mistaken, this lambda expression is semantically identical
// to the one above. However, the handler is not removed, indicating
// that a different delegate instance is constructed.
sender.Event -= (obj, args) => Console.WriteLine(_message);
// This prints "Hello, world!" again.
sender.Send();
}
}
Is there any reason why this behavior—one delegate instance for semantically identical anonymous methods—is not implemented?
You're mistaken to call it a literal, IMO. It's just an expression which is convertible to a delegate type.
Now as for the "interning" part - some lambda expressions are cached , in that for one single lambda expression, sometimes a single instance can be created and reused however often that line of code is encountered. Some are not treated that way: it usually depends on whether the lambda expression captures any non-static variables (whether that's via "this" or local to the method).
Here's an example of this caching:
using System;
class Program
{
static void Main()
{
Action first = GetFirstAction();
first -= GetFirstAction();
Console.WriteLine(first == null); // Prints True
Action second = GetSecondAction();
second -= GetSecondAction();
Console.WriteLine(second == null); // Prints False
}
static Action GetFirstAction()
{
return () => Console.WriteLine("First");
}
static Action GetSecondAction()
{
int i = 0;
return () => Console.WriteLine("Second " + i);
}
}
In this case we can see that the first action was cached (or at least, two equal delegates were produced, and in fact Reflector shows that it really is cached in a static field). The second action created two unequal instances of Action for the two calls to GetSecondAction, which is why "second" is non-null at the end.
Interning lambdas which appear in different places in the code but with the same source code is a different matter. I suspect it would be quite complex to do this properly (after all, the same source code can mean different things in different places) and I would certainly not want to rely on it taking place. If it's not going to be worth relying on, and it's a lot of work to get right for the compiler team, I don't think it's the best way they could be spending their time.
Is there any reason why this behavior—one delegate instance for semantically identical anonymous methods—is not implemented?
Yes. Because spending time on a tricky optimization that benefits almost no one takes time away from designing, implementing, testing and maintaining features that do benefit people.
The other answers bring up good points. Mine really doesn't have to do with anything technical - Every feature starts out with -100 points.
In general, there is no distinction between string variables which refer to the same String instance, versus two variables which refer to different strings that happen to contain the same sequence of characters. The same is not true of delegates.
Suppose I have two delegates, assigned to two different lambda expressions. I then subscribe both delegates to an event handler, and unsubscribe one. What should be the result?
It would be useful if there were a way in vb or C# to designate that an anonymous method or lambda which does not make reference to Me/this should be regarded as a static method, producing a single delegate which could be reused throughout the life of the application. There is no syntax to indicate that, however, and for the compiler to decide to have different lambda expressions return the same instance would be a potentially breaking change.
EDIT I guess the spec allows it, even though it could be a potentially breaking change if any code were to rely upon the instances being distinct.
This is allowed because the C# team cannot control this. They heavily rely on the implementation details of delegates (CLR + BCL) and the JIT compiler's optimizer. There already is quite a proliferation of CLR and jitter implementations right now and there is little reason to assume that's going to end. The CLI spec is very light on rules about delegates, not nearly strong enough to ensure all these different teams will end up with an implementation that guarantees that delegate object equality is consistent. Not in the least because that would hamper future innovation. There's lots to optimize here.
Lambdas can't be interned because they use an object to contain the captured local variables. And this instance is different every time you you construct the delegate.

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?
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?
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.
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.

Categories