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

标签 c# lambda

本文指出 You Can’t Unsubscribe from an Event Using a Lambda Expression .

例如您可以通过以下方式订阅:

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

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

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

为什么?这与取消订阅委托(delegate)有什么区别,例如

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

// ...

d.Barked -= handler;

最佳答案

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

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

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

然而,它没有做的是比较许多具有相似主体的 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;

其中 MethodOneMethodTwo 里面有相同的代码;它不会退订任何内容。

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

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

关于c# - 为什么我不能使用 Lambda 表达式取消订阅事件?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25563518/

相关文章:

c++ - 插入 vector 时使用 lambda 的返回值而不是函数的返回值有意义吗?

amazon-web-services - AWS Lambda 和网关 API 集成,返回状态代码 500

c# - 在通用列表的 ForEach() 的 lambda 表达式中使用条件运算符?

python - 在数据框行上运行以减少重复对 Python

c# - Dapper with .NET Core - 注入(inject)的 SqlConnection 生命周期/范围

c# - visual studio 中的命名空间在哪里

c# - 如何在 C# 中使用 Linq to SQL 将匿名类型转换为 List <dynamic>

c# - 如何在 C# 中合并列表<List<String>>

c# - 如何在 C# 中使用 ResolveIpNetEntry2

c# - Windows 8 - 如何正确获取嵌套文件夹?