Understanding Delegates
A delegate in VB.NET is a type-safe reference to a method, declared with the Delegate keyword — for example, Public Delegate Sub NotifyHandler(message As String) defines a delegate type that can point to any Sub matching that exact signature. Once declared, you can create a variable of that delegate type, assign it a method using AddressOf, and invoke it as if it were a method call; delegates are the mechanism underlying every event in .NET, and they're what makes callbacks and event handlers possible without resorting to unsafe function pointers.
Cricket analogy: A delegate is like a designated 12th man contract that specifies the exact role (fielding substitute) — any player assigned to it must fit that precise role, just as any method assigned to a delegate must match its exact signature.
Defining and Raising Custom Events
Public Class Thermostat
Public Event TemperatureChanged(sender As Object, newTemp As Double)
Private _temp As Double
Public Sub SetTemperature(value As Double)
_temp = value
RaiseEvent TemperatureChanged(Me, _temp)
End Sub
End ClassThe Event keyword declares a member that other code can subscribe to, and internally the compiler generates a multicast delegate to hold the list of subscribers. Inside the class that owns the event, RaiseEvent fires it, invoking every subscribed handler in turn with the arguments supplied; outside code cannot call RaiseEvent directly — it can only subscribe with AddHandler or (for WithEvents fields) the Handles clause — which enforces the one-way notification model where only the publisher decides when the event fires.
Cricket analogy: RaiseEvent firing to every subscriber is like a stadium's PA announcer broadcasting a wicket to every fan simultaneously — each listener reacts independently to the same single announcement.
Handling Events with WithEvents and Handles
VB.NET offers a distinctive way to subscribe to events declaratively: declaring a field with WithEvents, as in Private WithEvents _thermo As New Thermostat(), lets any Sub in that class attach itself with a Handles clause, such as Private Sub OnTempChanged(...) Handles _thermo.TemperatureChanged. This binding is resolved at compile time and remains fixed for the lifetime of the object referenced by that field, whereas reassigning the WithEvents field to a new object automatically re-subscribes the handler to the new instance and unsubscribes from the old one.
Cricket analogy: A WithEvents field automatically re-subscribing when reassigned is like a team's designated wicketkeeper role transferring cleanly to a new player the moment the starting XI changes, without needing to manually reassign gloves.
For controls or objects not declared with WithEvents — such as ones created dynamically at runtime — use AddHandler someObject.SomeEvent, AddressOf Me.HandlerMethod to subscribe, and RemoveHandler someObject.SomeEvent, AddressOf Me.HandlerMethod to unsubscribe explicitly. This dynamic approach is the only option when you don't know at compile time which instance you'll be subscribing to.
Multicast Delegates and the EventHandler Pattern
Delegates in .NET are multicast: a single delegate instance internally maintains an invocation list, so multiple methods can be combined onto one delegate variable with the += operator (or AddHandler), and invoking the delegate calls each method in the list in order. The .NET Framework convention — followed idiomatically in VB.NET — is to declare events using the standard EventHandler or EventHandler(Of TEventArgs) delegate type, with a signature of (sender As Object, e As EventArgs), so that event consumers across any .NET class can rely on a consistent, predictable pattern rather than each class inventing its own custom delegate shape.
Cricket analogy: A multicast delegate invoking every subscriber in order is like a team huddle where the captain's instruction gets relayed down a fixed chain — bowler, then keeper, then slip cordon — each reacting in sequence.
If you subscribe to an event with AddHandler (or WithEvents) but never unsubscribe with RemoveHandler when the subscriber object is no longer needed, the publisher's invocation list keeps a live reference to the subscriber. This prevents the garbage collector from reclaiming the subscriber's memory — a classic .NET memory leak pattern, especially dangerous in long-lived publishers like a singleton service subscribed to by short-lived forms.
Lambda Expressions as Delegates
Since VB.NET added lambda expression support, you can assign an inline Function or Sub lambda directly to a delegate-typed variable or parameter without declaring a separate named method — for instance, AddHandler btn.Click, Sub(s, e) MessageBox.Show('Clicked') attaches an anonymous handler on the spot. This is convenient for short, one-off handlers, though for handlers you'll need to remove later with RemoveHandler, you must keep a reference to the exact same delegate instance, since a newly written identical-looking lambda is a different object and won't match for removal.
Cricket analogy: An inline lambda handler is like a substitute fielder called up for just one over rather than signed to the squad for the whole tournament — quick, situational, and not meant to be a permanent named player.
- A delegate is a type-safe reference to a method, declared with Delegate and assigned via AddressOf.
- Event declares a publisher-controlled notification member; only the owning class can call RaiseEvent to fire it.
- WithEvents plus the Handles clause statically wires event handlers at compile time and re-subscribes automatically on reassignment.
- AddHandler/RemoveHandler subscribe and unsubscribe dynamically, required for runtime-created objects or controls.
- Delegates are multicast — combining handlers with += builds an invocation list called in order when the event fires.
- The standard EventHandler(sender, e) signature is the idiomatic .NET pattern for consistent event consumption.
- Forgetting RemoveHandler on long-lived publishers causes memory leaks by keeping subscribers alive via the invocation list.
Practice what you learned
1. What keyword is used in VB.NET to declare a type-safe method reference?
2. Who is allowed to call RaiseEvent for a given event?
3. What happens when you reassign a WithEvents field to a new object instance?
4. Why is a forgotten RemoveHandler call a common cause of memory leaks?
5. What is a limitation of using an inline lambda as an AddHandler subscriber if you plan to RemoveHandler it later?
Was this page helpful?
You May Also Like
Windows Forms Basics
Get started with Windows Forms in VB.NET — forms, controls, the visual designer, event handling, layout, and the form lifecycle.
Async/Await in VB.NET
Learn how the Async and Await keywords let VB.NET applications perform non-blocking I/O and long-running work without freezing the UI or wasting threads.
LINQ in VB.NET
Explore Language Integrated Query (LINQ) in VB.NET, from query syntax and lambda-based method syntax to deferred execution and aggregate operations.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics