为什么不能使用Lambda表达式取消订阅事件? [英] Why can't I unsubscribe from an Event Using a Lambda Expression?

查看:63
本文介绍了为什么不能使用Lambda表达式取消订阅事件?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

本文指出您无法使用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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆