Skip to content
AgileLogics
6 min read

Events

Prerequisites: Lambdas and delegates

An event lets an object announce that something happened, without knowing who's listening. Buttons, timers, and file watchers all work this way — and your own classes can too.

Declaring and raising an event

Public Class Stock
    Public ReadOnly Property Symbol As String
    Private _price As Decimal

    Public Event PriceChanged(sender As Object, oldPrice As Decimal, newPrice As Decimal)

    Public Sub New(symbol As String, price As Decimal)
        Me.Symbol = symbol
        _price = price
    End Sub

    Public Property Price As Decimal
        Get
            Return _price
        End Get
        Set(value As Decimal)
            If value = _price Then Return
            Dim old = _price
            _price = value
            RaiseEvent PriceChanged(Me, old, value)
        End Set
    End Property
End Class

Module Program
    Sub LogChange(sender As Object, oldPrice As Decimal, newPrice As Decimal)
        Dim s = DirectCast(sender, Stock)
        Console.WriteLine($"{s.Symbol}: {oldPrice} -> {newPrice}")
    End Sub

    Sub Main()
        Dim acme As New Stock("ACME", 100D)
        AddHandler acme.PriceChanged, AddressOf LogChange
        AddHandler acme.PriceChanged,
            Sub(sender, oldPrice, newPrice)
                If newPrice > oldPrice * 1.05D Then Console.WriteLine("  big jump!")
            End Sub

        acme.Price = 102D
        acme.Price = 110D
        acme.Price = 110D

        RemoveHandler acme.PriceChanged, AddressOf LogChange
        acme.Price = 90D
        Console.WriteLine("done")
    End Sub
End Module
ACME: 100 -> 102
ACME: 102 -> 110
  big jump!
done
  • Public Event Name(parameters) declares an event.
  • RaiseEvent Name(...) fires it; every handler runs, in the order it was added. Raising an event with no handlers does nothing.
  • AddHandler / RemoveHandler attach and detach handlers at run time — a named procedure (AddressOf) or a lambda.

The standard pattern: EventHandler(Of T)

.NET events conventionally pass a sender and an EventArgs object:

Public Class DownloadEventArgs
    Inherits EventArgs
    Public Property Percent As Integer
End Class

Public Class Downloader
    Public Event ProgressChanged As EventHandler(Of DownloadEventArgs)

    Public Sub Run()
        For p = 25 To 100 Step 25
            RaiseEvent ProgressChanged(Me, New DownloadEventArgs With {.Percent = p})
        Next
    End Sub
End Class

Module Program
    Sub Main()
        Dim d As New Downloader()
        AddHandler d.ProgressChanged, Sub(sender, e) Console.WriteLine($"{e.Percent}%")
        d.Run()
    End Sub
End Module
25%
50%
75%
100%

WithEvents and Handles

VB has a declarative way to connect handlers, used heavily by the Windows Forms designer:

Public Class Alarm
    Public Event Ring(message As String)
    Public Sub Trigger()
        RaiseEvent Ring("Wake up!")
    End Sub
End Class

Module Program
    WithEvents clock As New Alarm()

    Sub OnRing(message As String) Handles clock.Ring
        Console.WriteLine($"Alarm says: {message}")
    End Sub

    Sub Main()
        clock.Trigger()
    End Sub
End Module
Alarm says: Wake up!

A variable declared WithEvents can have procedures marked Handles variable.EventName. In a form, you'll see exactly this: Private Sub SaveButton_Click(sender As Object, e As EventArgs) Handles SaveButton.Click.

Common mistakes

  • Forgetting RemoveHandler for short-lived listeners of long-lived objects — the handler keeps the listener alive (a memory leak).
  • Doing slow work in a handler: every other handler (and the UI) waits for it.