为什么可以';t我使用Lambda表达式取消订阅事件

本文关键字:表达式 Lambda 取消 事件 为什么 | 更新日期: 2023-09-27 18:19:52

本文指出,您不能使用Lambda表达式取消订阅事件。

例如,您可以订阅如下:

d.Barked += (s, e) => Console.WriteLine("Bark: {0}", e);

但你不能这样取消订阅:

d.Barked -= (s, e) => Console.WriteLine("Bark: {0}", e); 

为什么?这与取消预订代表有什么区别,例如

EventHandler<string> handler = (s, e) => Console.WriteLine("Bark: {0}", e);
d.Barked += handler;
// ...
d.Barked -= handler;

为什么可以';t我使用Lambda表达式取消订阅事件

这一切都归结为:出于委托加法/减法的目的,两个委托何时被视为相同。当您取消订阅时,它本质上是使用Delegate.Remove中的逻辑,如果.Target.Method都匹配,则认为两个委托是等效的(至少,对于具有单个目标方法的委托的简单情况;多播更难描述)。那么:lambda上的.Method.Target是什么(假设我们将其编译为委托,而不是表达式)?

编译器实际上在这里有很大的自由度,但发生的是:

  • 如果lambda包含参数或变量的闭包,则编译器在编译器生成的表示捕获上下文(也可以包括this令牌)的类上创建一个方法(方法);目标是对此捕获上下文实例的引用(将由捕获范围定义)
  • 如果lambda不包括参数或变量的闭包,而是通过this(隐式或显式)使用每个实例的状态,则编译器会在当前类型上创建一个实例方法(方法);目标是当前实例(this
  • 否则编译器会创建一个静态方法(方法),目标为null(顺便说一句,在这种情况下,它还包括一个用于缓存单个静态委托实例的漂亮字段-因此在这种情况中,每个lambda只创建一个委托)

然而,它没有做的是,将许多体型相似的羊羔进行比较,以减少任何体型。因此,当我编译您的代码时,我得到的是两个静态方法:

[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);
}

(这里的Main只是因为在我的测试设备中,这些Lambda在Main方法中,但最终编译器可以选择它在这里选择的任何发音不清的名称)

第一种方法由第一个lambda使用;第二方法由第二lambda使用。所以最终,它不起作用的原因是.Method不匹配。

用C#的常规术语来说,这就像做:

obj.SomeEvent += MethodOne;
obj.SomeEvent -= MethodTwo;

其中CCD_ 12和CCD_;它不会取消订阅任何内容。

如果编译器发现了这一点,可能会很好,但不需要,因此不选择更安全-这可能意味着不同的编译器开始产生非常不同的结果。

作为旁注;如果确实尝试去重复,可能会非常令人困惑,因为您还会遇到捕获上下文的问题-在某些情况下它"有效",而在其他情况下则无效-但不清楚哪一种情况可能是最糟糕的情况。