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/RemoveHandlerattach 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
RemoveHandlerfor 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.