为什么我不能使用Lambda表达式取消订阅事件?

时间:2014-08-29 07:26:22

标签: c# lambda

本文说明You Can’t Unsubscribe from an Event Using a Lambda Expression

E.g。您可以按以下方式订阅:

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;

1 个答案:

答案 0 :(得分:37)

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

编译器实际上有很多自由,但发生的是:

  • 如果lambda包含对参数或变量的闭包,编译器会在编译器生成的类上创建一个方法(方法),该类表示捕获上下文(也可以包含{ {1}}令牌); target 是对此捕获上下文实例的引用(将由捕获范围定义)
  • 如果lambda不包含对参数或变量的闭包,但确实通过this(隐式或显式)使用每个实例状态,则编译器会创建一个实例方法(方法)对当前类型; 目标是当前实例(this
  • 否则编译器会创建静态方法(方法),目标为空(顺便说一句,在这种情况下,它还包含一个漂亮的字段来缓存单个静态委托实例 - 所以在这种情况下,每个lambda只创建一个委托)
然而, 所做的是比较许多具有相似外观的lambda以减少任何。所以我编译代码时得到的是两个静态方法:

this

(这里的[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); } 只是因为在我的测试装备中,这些lambda属于Main方法 - 但最终编译器可以选择它在这里选择的任何不可发音的名称。

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

在常规的C#术语中,它就像在做:

.Method

其中obj.SomeEvent += MethodOne; obj.SomeEvent -= MethodTwo; MethodOne内部具有相同的代码;它没有取消订阅。

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

作为旁注;如果它尝试去重复,那可能会非常混乱,因为你也有捕获上下文的问题 - 那么它会在某些情况下“工作”而不是其他情况 - 没有明显的 - 可能是最糟糕的情况。