Events and Delegates

22.1 Delegates

Public Delegate Function MathOperation(a As Integer, b As Integer) As Integer

Function Add(a As Integer, b As Integer) As Integer
    Return a + b
End Function

Dim op As MathOperation = AddressOf Add
Console.WriteLine(op(3, 4))                 ' 7
Dim op2 As MathOperation = Function(a, b) a * b
Console.WriteLine(op2.Invoke(3, 4))         ' 12

' Built-in generic delegates
Dim square As Func(Of Integer, Integer) = Function(x) x * x
Dim log As Action(Of String) = Sub(msg) Console.WriteLine(msg)
Dim isEven As Predicate(Of Integer) = Function(n) n Mod 2 = 0
Dim compare As Comparison(Of String) = Function(a, b) a.Length.CompareTo(b.Length)

' Multicast delegates
Dim notify As Action(Of String) = Sub(m) Console.WriteLine("Email: " & m)
notify = CType([Delegate].Combine(notify, New Action(Of String)(Sub(m) Console.WriteLine("SMS: " & m))), Action(Of String))
notify("Order shipped")

22.2 Declaring and Raising Events

Public Class TemperatureSensor
    Public Event TemperatureChanged(sender As Object, e As TemperatureEventArgs)   ' event declaration
    Public Event Alarm As EventHandler                                               ' standard EventHandler

    Private _temp As Double
    Public Property Temperature As Double
        Get
            Return _temp
        End Get
        Set(value As Double)
            If value <> _temp Then
                _temp = value
                RaiseEvent TemperatureChanged(Me, New TemperatureEventArgs(value))
                If value > 100 Then RaiseEvent Alarm(Me, EventArgs.Empty)
            End If
        End Set
    End Property
End Class

Public Class TemperatureEventArgs
    Inherits EventArgs
    Public ReadOnly Property Value As Double
    Public Sub New(value As Double)
        Me.Value = value
    End Sub
End Class

22.3 Handling Events: WithEvents/Handles (VB Style)

Handles wires handlers declaratively. That's how WinForms connects Button1.Click handlers.

One handler can handle multiple events: Handles btnOk.Click, btnCancel.Click.

Public Class Monitor
    Private WithEvents _sensor As New TemperatureSensor()

    Private Sub OnTempChanged(sender As Object, e As TemperatureEventArgs) Handles _sensor.TemperatureChanged
        Console.WriteLine($"New temperature: {e.Value}")
    End Sub

    Private Sub OnAlarm(sender As Object, e As EventArgs) Handles _sensor.Alarm
        Console.WriteLine("ALARM!")
    End Sub

    Public Sub Run()
        _sensor.Temperature = 25
        _sensor.Temperature = 105
    End Sub
End Class

22.4 Handling Events Dynamically: AddHandler/RemoveHandler

Dim sensor As New TemperatureSensor()
AddHandler sensor.TemperatureChanged, AddressOf ShowTemp
AddHandler sensor.Alarm, Sub(s, e) Console.WriteLine("Lambda alarm handler")
sensor.Temperature = 50
RemoveHandler sensor.TemperatureChanged, AddressOf ShowTemp     ' prevents memory leaks

22.5 Custom Events (Advanced)

Private _handlers As New List(Of EventHandler)
Public Custom Event DataReady As EventHandler
    AddHandler(value As EventHandler)
        _handlers.Add(value)
    End AddHandler
    RemoveHandler(value As EventHandler)
        _handlers.Remove(value)
    End RemoveHandler
    RaiseEvent(sender As Object, e As EventArgs)
        For Each h In _handlers
            h.Invoke(sender, e)
        Next
    End RaiseEvent
End Event

22.6 INotifyPropertyChanged (Data Binding)

Imports System.ComponentModel
Imports System.Runtime.CompilerServices

Public Class ViewModelBase
    Implements INotifyPropertyChanged
    Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged

    Protected Function SetProperty(Of T)(ByRef field As T, value As T, <CallerMemberName> Optional name As String = Nothing) As Boolean
        If EqualityComparer(Of T).Default.Equals(field, value) Then Return False
        field = value
        RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(name))
        Return True
    End Function
End Class