自定义事件 - 调用列表实现注意事项

时间:2010-04-09 16:03:51

标签: .net vb.net custom-event custom-events

我正在寻找一些关于在VB.NET中实现自定义事件的指针(Visual Studio 2008,.NET 3.5)。

我知道“常规”(非自定义)事件实际上是Delegates,所以我在实现自定义事件时考虑使用Delegates。另一方面,Andrew Troelsen"Pro VB 2008 and the .NET 3.5 Platform"书籍在其所有自定义事件示例中使用了集合类型,而Microsoft的sample codes则与该思路相匹配。

所以我的问题是:在选择一种设计而不是另一种设计时,我应该考虑哪些因素?每种设计的优缺点是什么?其中哪些类似于“常规”事件的内部实现?

以下是展示这两种设计的示例代码。

Public Class SomeClass
    Private _SomeEventListeners As EventHandler
    Public Custom Event SomeEvent As EventHandler
        AddHandler(ByVal value As EventHandler)
            _SomeEventListeners = [Delegate].Combine(_SomeEventListeners, value)
        End AddHandler

        RemoveHandler(ByVal value As EventHandler)
            _SomeEventListeners = [Delegate].Remove(_SomeEventListeners, value)
        End RemoveHandler

        RaiseEvent(ByVal sender As Object, ByVal e As System.EventArgs)
            _SomeEventListeners.Invoke(sender, e)
        End RaiseEvent
    End Event

    Private _OtherEventListeners As New List(Of EventHandler)
    Public Custom Event OtherEvent As EventHandler
        AddHandler(ByVal value As EventHandler)
            _OtherEventListeners.Add(value)
        End AddHandler

        RemoveHandler(ByVal value As EventHandler)
            _OtherEventListeners.Remove(value)
        End RemoveHandler

        RaiseEvent(ByVal sender As Object, ByVal e As System.EventArgs)
            For Each handler In _OtherEventListeners
                handler(sender, e)
            Next
        End RaiseEvent
    End Event
End Class

2 个答案:

答案 0 :(得分:3)

我会说使用简单的委托;简单地说 - 它已经已经(内部)一个列表,因此您可以通过将其包装在List<T>中来重复工作。您还有额外列表对象和数组的开销,可能为null,因此对垃圾收集等影响更大。

此外,如果你正在做 lot ,请考虑EventHandlerList,它存在以提供对稀疏事件的有效访问 - 即你想暴露的地方许多事件,但其中许多可以取消分配。

第一个示例更接近“标准”事件(尽管您可能希望在调用Invoke时注意未分配/空处理程序,因为它可能为null)。另外,请注意,有些语言(老实说我不知道​​VB在这里做了什么)将同步应用于事件,但实际上很少有事件真的需要是线程安全的,所以这可能是过度的。

编辑还会发现它们之间存在功能差异:

  • 委托方法使用相同的目标方法/实例处理不同的实例(我不认为List<T>会)
  • 重复委托:委托删除最后一个;列表删除最早的第一个
  • 复合:添加A,添加B,删除(A + B) - 使用委托,这应该产生null / empty,但列表方法将保留A和B(使用(A + B)删除无法找到任何东西)

答案 1 :(得分:0)

使用MulticastDelegate来保存订阅事件列表当然是一种可行的方法,但不是我特别热衷的方法。要从MulticastDelegate添加或删除事件,必须执行以下两项操作之一:

  1. 获取一个锁,从旧的委托创建一个新委托,但添加或删除一个事件,将委托指针设置为该委托,然后释放锁
  2. 复制对旧委托的引用,从中创建新委托,但添加或删除事件时,如果旧的委托未更改,请使用Interlocked.CompareExchange存储对新委托的引用,并且如果CompareExchange失败,则全面启动。

如果没有争用,后一种方法可能会提供稍好的性能,但如果许多线程同时尝试添加或删除事件,则可能表现不佳。然而,后一种方法的一个优点是,在握住锁定时没有线程死亡的危险。

这两种方法都不是特别干净。如果计划通过简单地调用委托来调用所有事件,则事件调用性能可能会抵消添加/删除性能。另一方面,如果计划使用GetInvocationList以便在try / catch块中包装事件调用,那么最好只使用(适当锁定的)列表或其他此类数据结构。