Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

c#

lambda

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;
like image 443
DaveDev Avatar asked Aug 29 '14 07:08

DaveDev


People also ask

How do I subscribe to an event with a lambda expression?

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.

What is the difference between lambda function and context?

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.

How do I test lambdas?

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:

How do lambda functions work with events?

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.


1 Answers

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.

like image 61
Marc Gravell Avatar answered Oct 05 '22 20:10

Marc Gravell