Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
193 views
in Technique[技术] by (71.8m points)

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

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;
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...