【问题标题】:Problem with event "chaining"事件“链接”的问题
【发布时间】:2011-01-02 13:53:03
【问题描述】:

注意:我编辑了这个问题是为了让其他有同样问题的人更容易在这里获得帮助。要查看更适合某些答案的原始问题,请查看编辑历史记录。

在一个项目中,我有一个 ExecutionManager 类,它可以包含多个 ExecutionSlot 的实例。 ExecutionSlot 类有几个公共事件字段,如下所示:

public event EventHandlers.ObjectEventHandler<IPlugin> ExecuteCompleted;

对于这些事件中的每一个,在 ExecutionManager 上都有一个匹配的事件。期望的行为是每次 ExecutionSlot 引发一个事件时,匹配的事件也会在包含的 ExecutionManager 上引发。

实现的解决方案是,每当将 ExecutionSlot 添加到 ExecutionManager 时,ExectionManager 都会将自己的事件添加到 ExecutionSlot,如下所示:

executionSlot.ExecuteCompleted += ExecuteCompleted;

目前还不需要移除 ExecutionSlot,因此事件也永远不会被移除。

问题是没有引发 ExecutionManager 上的事件。在确认 ExecutionSlot 正在触发事件后,我发现将上面的行更改为以下内容可以解决问题:

executionSlot.ExecuteCompleted += (sender, eventArgs) => ExecuteCompleted(sender, eventArgs);

我不知道为什么,所以我的问题是,有什么区别。

造成这种差异的原因是第一个将 ExecutionManager 事件的当前侦听器添加到 ExecutionSlot 事件中。因此,在引发事件时,不会调用以后添加的任何侦听器。 相比之下,后一种解决方案使用 lambda 来引发 ExecutionManager 的事件,这意味着将调用事件发生时的侦听器。

第一个解决方案失败的根本原因是委托是不可变的。因此,当您向事件添加新委托时,您实际上是在创建一个新委托,其中包含现有委托和添加的委托。所以之前对委托的任何引用都不会包含新添加的委托。

【问题讨论】:

  • 也许可以将您的编辑恢复为原来的问题,然后提出另一个问题并回答它,来回引用?
  • 我明白保留原文的意义,因为这就是这里的答案。但由于原文不够清晰,我觉得比起几乎重复的问题,新的文本对其他人的帮助会更好。

标签: c# .net events .net-3.5 delegates


【解决方案1】:

一个想法......也许你的代码中有你正在做的地方:

executionSlot.ExecuteCompleted -= ExecuteCompleted;

如果您使用原始订阅语法,它将取消订阅该事件,但在您进行更改后不会将其删除。

【讨论】:

  • 那不行,不会生成相同的委托对象实例。目标不同。
  • 感谢您的建议,可惜没有退订。但我更喜欢后一种代码工作的原因之一是我可能需要在程序的更高版本中使用它,而且我真的更不想写出处理函数,因为有很多事件。我会尝试并在问题中提供更多信息。
【解决方案2】:

编辑:这个答案假设ExecuteCompleted 是一个方法。因为它实际上是一个字段,所以它彻底改变了一切。为了后代,我将把这个答案留在这里。

第一个版本添加了一个事件处理程序,该处理程序带有一个从自动生成的方法创建的委托,该方法又只调用ExecuteCompleted。有点像这样:

private void <>AutogeneratedMethodWithUnspeakableName(object sender, EventArgs e)
{
    ExecuteCompleted(e);
}
...
executionSlot.ExecuteCompleted += <>AutogeneratedMethodWithUnspeakableName;

第二个版本添加了一个事件处理程序,其中包含直接从 ExecuteCompleted 方法创建的委托。

基本上,第一种形式是一个额外的重定向级别。这通常不会有任何区别,除了 JoelFan 提到的取消订阅。我会这就是问题所在。

引发事件的类可以反映附加的处理程序并查看方法名称,在这种特殊情况下做出不同的反应 - 但不太可能。

【讨论】:

  • 感谢您的建议,可惜没有退订。但我更喜欢后一种代码工作的原因之一是我可能需要在程序的更高版本中使用它,而且我真的更不想写出处理函数,因为有很多事件。而且我也没有在课堂上使用反射来引发事件。我会尝试并在问题中提供更多信息。
  • @Lillemanden:如果你能想出一个简短但完整的例子来说明问题,我相信我们能够解决它。
  • @Jon Skeet:我很确定 Protron 成功了。由于委托是不可变的,因此每次使用“+=”时都会创建一个新的多播委托。因此,当订阅链中的最后一个事件时,会创建一个新的委托实例,但该实例不会添加到 executionSlot.ExecuteCompleted 事件中。至少那是 AFAIK,希望它有意义。
  • @Lillemanden:我一直假设 ExecuteCompleted 是您代码中的一个方法。不是吗?如果你能制作一个简短但完整的程序来演示这一点,那真的很有帮助......
  • 这是一个事件字段。午饭后我会添加一些代码来提问;)
【解决方案3】:

我相信这里发生的事情是在第一个示例中创建了某种临时对象,其中调用了一个空事件处理程序,但什么也不做。

您说的第二个示例是有效的,它是使用真实代码在对象上的事件处理程序。虽然不完全确定那里发生了什么,但这是我最好的猜测。

当然,第一个例子闻起来很糟糕,因为它使用 lambda 表达式来混淆含义,没有真正的附加值。

【讨论】:

  • 它实际上可能没有创建一个对象 - 我希望编译器向当前类添加一个实例方法,因为它不会捕获除“this”之外的任何内容。
【解决方案4】:

this other post on stackoverflow

b += (s, e) => a(s, e);

不一样

b += a;

它将a的当前内容附加到b,因此如果以后有更多的处理程序加入a,这不会导致它们在b被触发时被调用

【讨论】:

  • 这就是 a 和 b 是变量的地方——在这种情况下,它是一个方法的名称,所以它实际上并没有捕获除“this”引用之外的任何东西(无论如何都会在第二种形式中隐式捕获) )。
  • Frig,你说的完全正确。愚蠢的不可变委托;)这完美地解释了它,稍后连接到链中最后一个事件的对象将不会被调用。谢谢
  • 好吧,我一直假设 ExecuteCompleted 是一种方法。如果它实际上是一个变量(例如,通过类似字段的事件),那么这确实是有道理的。另一个有一个相当完整的例子会有所帮助的例子:)
  • 是的,确实是一个事件字段。我将添加一些代码,以便其他人也可以使用此问题。
猜你喜欢
  • 2010-09-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-10-31
相关资源
最近更新 更多