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

前端 未结 1 961
梦如初夏
梦如初夏 2020-12-14 20:21

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) => Consol         


        
1条回答
  •  有刺的猬
    2020-12-14 20:46

    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 
    b__0(object s, string e) { Console.WriteLine("Bark: {0}", e); } [CompilerGenerated] private static void
    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.

    0 讨论(0)
提交回复
热议问题