问题 哪种C#模式具有更好的性能以避免重复的事件处理程序?


避免重复注册事件处理程序基本上有两种模式: (根据这个讨论: C#模式防止事件处理程序挂钩两次

  1. 使用System.Linq命名空间,并通过调用检查事件处理程序是否已注册 GetInvocationList().Contains(MyEventHandlerMethod);

  2. 在注册之前取消注册,如下所示:

    MyEvent -= MyEventHandlerMethod;
    MyEvent += MyEventHandlerMethod;
    

我的问题是,性能方面,哪个更好,或者它们在性能方面有显着差异?


7212
2017-12-30 10:39


起源

说真的,这些操作非常快,除非你有成千上万的事件处理程序,否则在这种性能优化中应该没有实际的好处。你有这个问题的实际情况吗? - Enigmativity
我不能给出明确的答案,但我会假设 Contains() 因为 +=/-= 内部仍然需要遍历调用列表,然后操纵它两次。但正如Enigmativity所说,你不太可能遇到任何不同的情况 - Rhumborl
衡量并知道。不要猜测也不要猜测。首先担心你的其余代码,可能会有更大/更差/更困难的战斗,投资回报更高。 - Erno de Weerd
.NET Framework投入大量资金来打败像这样的“模式”。通过给事件添加/删除访问器,使您无法获取调用列表。主要是因为它根本不是一种模式,它是一种模式 窃听器。故意隐藏客户端代码中的错误或双重猜测客户端代码在订阅事件时的意图是一个错误。 - Hans Passant
如果(1)更快,C#编译器只会在写(2)时发出(1)。 - usr


答案:


根据 文件,调用列表存储为数组或类似的东西,并且也存储事件处理程序的顺序。可能存在内部结构以保持快速搜索特定方法。

所以在最糟糕的情况下操作 GetInvocationList().Contains(MyEventHandlerMethod); 是 O(1) (因为我们只是获得了数组的引用)+ O(n) 搜索方法,即使没有优化。我严重怀疑这是真的,我认为有一些优化代码,它是 O(log_n)

第二种方法有额外的添加操作,我认为, O(1),因为我们将事件处理程序添加到结尾。

因此,要了解这些操作之间的区别,您需要大量的事件处理程序。
!如果你使用 第二 正如我所说, 您将把事件处理程序添加到队列的末尾,在某些情况下可能是错误的。所以使用第一个,毫无疑问。


2
2017-12-30 10:50



downvote的任何原因? - VMAtm


我不认为这对假设的性能增益和实际差异都很重要。

GetInvocationList 和 -= 走内部阵列 _invocationList。 (看到 资源

LINQ扩展方法 Contains 将需要更多的时间,因为它需要遍历和转换,返回然后检查整个数组 Contains 本身。该 Contains 具有不需要添加事件处理程序的优点 如果 它存在意味着一些性能增益。


5
2017-12-30 10:49





  1. 不适用于外部呼叫者,效率不高 无论如何
  2. 应该没问题(请注意,每次创建2个委托实例),但也要考虑
  3. 在大多数情况下,应该很容易知道您是否已经订阅;如果你不知道,那表明一个架构问题

典型的用法是“订阅{某些用法} [取消订阅]”,其中取消订阅可能没有必要,具体取决于活动发布者和订阅者的相对生命周期;如果你真的有一个可重入的场景,那么“订阅,如果还没有订阅”本身就有问题,因为什么时候 以后取消订阅,你不知道你是否阻止了外部迭代接收事件。


4
2017-12-30 10:55



作为一种精度,在许多情况下,您可以在应该只运行一次的代码中添加处理程序(构造函数,Load处理程序)并在Dispose方法中删除它... - Phil1970


MyEvent -= MyEventHandlerMethod首先需要在调用列表中找到已注册的事件处理程序才能将其删除。 所以 GetInvocationList().Contains 更好,但它确实无足轻重。

但是,请注意您无法访问 event EventHandler foo的调用清单....


2
2017-12-30 10:51



“请注意,如果对象拥有事件处理程序,则无法访问事件EventHandler foo的调用列表”。 - Patrick Hofman