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.
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…