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

Dav*_*Dev 15 c# lambda

本文说明您无法使用Lambda表达式取消订阅事件.

例如,您可以订阅如下:

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

但是你不能这样取消订阅:

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

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

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

// ...

d.Barked -= handler;
Run Code Online (Sandbox Code Playgroud)

Mar*_*ell 38

这一切都归结为:为了代表加法/减法的目的,两位代表何时被认为是相同的.当你取消订阅时,它实质上是使用逻辑from Delegate.Remove,它考虑两个代表是否相同(如果是.Target.Method匹配的话)(至少对于具有单个目标方法的委托的简单情况;多播要描述的更复杂).所以:什么是.Method.Target上一个lambda(假设我们将它编译成一个代表,而不是一种表达)?

编译器实际上在这里有很多自由,但是会发生什么:

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

然而,它没有做的是将许多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);
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

obj.SomeEvent += MethodOne;
obj.SomeEvent -= MethodTwo;
Run Code Online (Sandbox Code Playgroud)

在哪里MethodOne,MethodTwo并在其中有相同的代码; 它没有取消订阅任何东西.

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

作为旁注; 如果它确实试图去重复它可能会非常混乱,因为你也有捕获上下文的问题 - 那么它会在某些情况下"工作"而不是其他情况 - 而不是显而易见的 - 可能最糟糕的情况.