为什么不能使用Lambda表达式取消订阅事件? [英] Why can't I unsubscribe from an Event Using a Lambda Expression?
问题描述
本文指出您无法使用Lambda表达式取消订阅事件。
例如您可以按以下方式订阅:
d。吠叫+ =(s,e)=> Console.WriteLine( Bark:{0},e);
但是您不能像这样退订:
d。吠叫-=(s,e)=> Console.WriteLine( Bark:{0},e);
为什么?此操作与取消订阅代表有什么区别,例如
EventHandler< string>处理程序=(s,e)=> Console.WriteLine( Bark:{0},e);
d。吠叫+ =处理程序;
// ...
d。
全部归结为:什么时候考虑两个代表代表加/减的目的相同。取消订阅时,它实际上是使用 Delegate.Remove
中的逻辑,如果两个 .Target
和 .Method
匹配(至少,对于具有单个目标方法的委托的简单情况;多播的描述更为复杂)。因此:lambda上的 .Method
和 .Target
是什么(假设我们将其编译为
实际上,编译器在这里有很多自由度,但是是什么?发生是:
- 如果lambda包含对参数或变量的闭包,则编译器会创建一个方法(方法)在代表捕获上下文的编译器生成的类上(还可以包含
this
标记); target 是对此捕获上下文实例的引用(将由捕获范围定义) - 如果lambda不包含对a的闭包参数或变量,但确实通过
this
(隐式或显式)使用每个实例的状态,编译器创建了一个实例方法(方法 )当前类型; target 是当前实例(this
) - 否则,编译器将创建一个静态方法( method ),而 target 为null(顺便说一下,在这种情况下,它还包含一个漂亮的字段来缓存单个静态委托实例-因此,在这种情况下, ,每个lambda只会创建一个委托)
不会做什么,比较具有相似外观的lambda,以减少它们。因此,当我编译您的代码时,我得到的是两个静态方法:
[CompilerGenerated]
private static void< Main> b__0(对象s,字符串e)
{
Console.WriteLine( Bark:{0},e);
}
[CompilerGenerated]
私有静态void< Main> b__2(object s,string e)
{
Console.WriteLine( Bark :{0},e);
}
( Main
这仅仅是因为在我的测试设备中,这些lambda位于 Main
方法中-但最终编译器可以选择在此处选择的任何无法发音的名称)
第一个方法由第一个lambda使用;第二个lambda使用第二种方法。所以最终,它不起作用的原因是因为 .Method
不匹配。
常规用C#术语来说,就像这样做:
obj.SomeEvent + = MethodOne;
obj.SomeEvent-= MethodTwo;
其中 MethodOne
和 MethodTwo
内部具有相同的代码;
如果编译器发现了这个问题,可能是好,但这不是必需的,因此更安全的是不会选择-这可能意味着不同的编译器开始产生截然不同的结果。
作为旁注;如果 did 尝试去重复,这可能会非常令人困惑,因为您还会遇到捕获上下文的问题-这样的话,在某些情况下它会起作用,而在其他情况下则不会-不明显的是-可能是最坏的情况。
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;
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.
这篇关于为什么不能使用Lambda表达式取消订阅事件?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!