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;
Note that the subscriber class needs a reference to the publisher class in order to subscribe to its events. You can also use a lambda expression to specify an event handler: If you don't have to unsubscribe from an event later, you can use the addition assignment operator ( +=) to attach an anonymous function as an event handler.
The lambda function is triggered, it will inspect its event and perform a logic on the event before returning and terminating. Now that we've covered event, let's move onto context. context is a Python objects that implements methods and has attributes.
When you hit the Test button on the console, you invoked the lambda and passed it the test event -- the lambda then took that event and printed it out. As we dive deeper into AWS Lambda, we'll find more and more possible events that can trigger lambdas, such as:
Each event will have a different format and carry different payloads, but the pattern is always the same. The lambda function is triggered, it will inspect its event and perform a logic on the event before returning and terminating. Now that we've covered event, let's move onto context.
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:
this
token); the target is the reference to this capture-context instance (which will be defined by the capture scope)this
(implicit or explicit), the compiler creates an instance method (the method) on the current type; the target is the current instance (this
)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.
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