Question

I'm looking for some pointers on implementing Custom Events in VB.NET (Visual Studio 2008, .NET 3.5).

I know that "regular" (non-custom) Events are actually Delegates, so I was thinking of using Delegates when implementing a Custom Event. On the other hand, Andrew Troelsen's "Pro VB 2008 and the .NET 3.5 Platform" book uses Collection types in all his Custom Events examples, and Microsoft's sample codes match that line of thought.

So my question is: what considerations should I have when choosing one design over the other? What are the pros and cons for each design? Which of these resembles the inner-implementation of "regular" events?

Below is a sample code demonstrating the two designs.

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
Was it helpful?

Solution

I would say use the simple delegate; simply - it is already (internally) a list, so you are duplicating effort by wrapping it in List<T>. You also have the overhead of extra list objects and arrays, that could be null, so more impact on garbage collection etc.

Also, if you are doing lots of this, consider EventHandlerList, which exists to provide efficient access to sparse events - i.e. where you want to expose lots of events but many of them can be unassigned.

The first example is far closer to "standard" events (although you might want to watch for unassigned / null handlers when calling Invoke, as it could be null). Additionally, note that some languages (I honestly don't know what VB does here) applies synchronization to events, but in reality very few events really need to be thread-safe, so that could be overkill.

edit it also occurs that there is a functional difference between them:

  • the delegate approach treats different instances with the same target method / instance as equal (I don't think the List<T> will)
  • duplicate delegates: delegate removes last first; list removes earliest first
  • composites: add A, add B, and remove (A+B) - with a delegate this should yield null / empty, but the list approach will retain A and B (with the (A+B) remove failing to find anything)

OTHER TIPS

Using a MulticastDelegate to hold a list of subscribed events is certainly a workable approach, but not one I'm particularly keen on. To add or remove an event from a MulticastDelegate, one must do one of two things:

  1. Acquire a lock, create a new delegate from the old one, but with an event added or removed, set the delegate pointer to that delegate, and then release the lock
  2. Copy a reference to the old delegate, create a new delegate from it, but with an event added or removed, use Interlocked.CompareExchange to store the reference to the new one if the old one is unchanged, and start all over if the CompareExchange failed.

The latter approach may offer slightly better performance if there's no contention, but may perform badly if many threads are simultaneously trying to add or remove events. One advantage of the latter approach, though, is that there's no danger of a thread dying while holding a lock.

Neither approach seems particularly clean. If one is planning on invoking all events by simply invoking a delegate, the event-invocation performance may offset the add/remove performance. On the other hand, if one plans to use GetInvocationList so as to wrap event calls in try/catch blocks, one may be better off just using a (suitably locked) list or other such data structure.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top