Events, Delegates, and CLR Event System Conventions
Microsoft Silverlight will reach end of support after October 2021. Learn more.
This topic describes some of the underlying concepts of the CLR event system, such as how delegates work.
The Purpose of Delegates
In event communication, the event sender class does not know which object or method will receive (handle) the events it raises. The .NET Framework defines a type (Delegate) that provides the intermediary between the event and its handlers. Technically, a delegate is a class that can hold a reference to a method. Unlike other classes, a delegate class has a signature, and it can hold references only to methods that match its signature. While delegates have other uses, the discussion here focuses on the event handling functionality of delegates. The following example shows an event delegate declaration.
Public Delegate Sub AlarmEventHandler(sender As Object, e As AlarmEventArgs)
public delegate void AlarmEventHandler(object sender, AlarmEventArgs e);
The syntax is similar to that of a method declaration; however, the delegate keyword informs the compiler that AlarmEventHandler is a delegate type. By convention, event delegates in the .NET Framework have two parameters, the source that raised the event, represented by the sender parameter, and the data for the event, represented by the e parameter.
Custom event delegates are needed only when an event generates distinctive event data that requires also defining an event data class. For events that do not generate event data, System.EventHandler is adequate.
Event delegates are multicast, which means that they can hold references to more than one event handling method. A delegate acts as an event dispatcher for the class that raises the event by maintaining a list of registered event handlers for the event. For details, see Delegate.
The event pattern and naming conventions for custom events
Using the On* pattern
Raising your custom event
Naming Conventions
By convention, the names that you use for events, delegates, and event data classes should share a common root if possible. For example, if you define a CapacityExceeded event in one of your classes, the delegate should be named CapacityExceededHandler, and event data should be named CapacityExceededEventArgs.
Events in the .NET Framework
Raising an Event
You can define events that are raised by your own classes, structures, and interfaces. The event can then be raised under a specified condition, such as the occurrence of a particular user action, a change in state, or a change in the value of a variable.
Note: |
---|
This section lists the steps involved in raising an event and presents code fragments from a larger example that illustrate each step. Consuming an event is discussed in the Consuming an Event section later in this topic, and the code to handle the events raised by the example code in this section is also presented there. |
Defining a custom event requires that you provide three interrelated elements:
A class that holds event data. By convention, this class should be named EventNameEventArgs. This class must inherit from System.EventArgs. In some cases, it may be possible to use an existing event data class rather than define a custom one. For example, if your event does not use custom data, you can use System.EventArgs for your event data.
The following example defines a class named AlarmEventArgs to hold custom event data. It inherits from EventArgs and adds three additional properties: Rings, which indicates the number of times the alarm should ring when the event is raised; Snooze, which indicates whether the alarm event should be raised repeatedly at a regular interval after it is first raised; and Cancel, which indicates that the alarm event should stop being raised.
Public Class AlarmEventArgs : Inherits EventArgs Private numberOfRings As Integer Private snoozePressed As Boolean Private cancelled As Boolean = False Public Sub New(snoozePressed As Boolean, numberOfRings As Integer) Me.snoozePressed = snoozePressed Me.numberOfRings = numberOfRings End Sub Public ReadOnly Property Rings As Integer Get Return Me.numberOfRings End Get End Property Public ReadOnly Property Snooze As Boolean Get Return Me.snoozePressed End Get End Property Public Property Cancel As Boolean Get Return Me.Cancelled End Get Set Me.Cancelled = Value End Set End Property End Class
public class AlarmEventArgs : EventArgs { private int numberOfRings; private bool snoozePressed; private bool cancelled = false; public AlarmEventArgs(bool snoozePressed, int numberOfRings) { this.snoozePressed = snoozePressed; this.numberOfRings = numberOfRings; } public int Rings { get { return this.numberOfRings; } } public bool Snooze { get { return this.snoozePressed; } } public bool Cancel { get { return this.cancelled; } set { this.cancelled = value; } } }
A delegate for the event. By convention, event delegates are named EventNameEventHandler and have two parameters: an Object that indicates the source of the event, and an object derived from System.EventArgs that provides information about the event. Depending on the object that provides event data, it may be possible to use an existing event handler. For example, if your event does not use custom event data, you can use EventHandler for your delegate.
The following example defines an event delegate named AlarmEventHandler that takes two parameters: a reference to the object that raised the event, and an AlarmEventArgs object that contains custom event data.
Public Delegate Sub AlarmEventHandler(sender As Object, e As AlarmEventArgs)
public delegate void AlarmEventHandler(object sender, AlarmEventArgs e);
A class, structure, or interface that provides the event declaration (EventName) and a method that raises the event. You define an event in your class using the event keyword.
You should raise the event by calling the protected OnEventName method from a derived class, if such a method is available. The OnEventName method raises the event by invoking the delegates and passing any event-specific data. The delegate methods for the event can perform actions for the event or process the event-specific data.
The following example defines an Alarm class. Its members include an AlarmEvent event and three properties, Rings, SnoozePressed, and Cancel, that correspond to the three properties of the AlarmEventArgs class. Members also include a protectedOnAlarm method that raises the event and a Start method that is responsible for calling the OnAlarm method at regular intervals.
public class AlarmClock { public event AlarmEventHandler AlarmEvent; protected int numberOfRings; protected bool snooze; public AlarmClock(int numberOfRings, bool snooze) { this.numberOfRings = numberOfRings; this.snooze = snooze; } public int Rings { get { return this.numberOfRings; } } public bool SnoozePressed { get { return this.snooze; } } protected virtual void OnAlarm(AlarmEventArgs e) { if (AlarmEvent != null) AlarmEvent(this, e); } public void Start() { AlarmEventArgs e = new AlarmEventArgs(snooze, numberOfRings); do { System.Threading.Thread.Sleep(1500); OnAlarm(e); } while (! e.Cancel); } }
Note: The protected OnEventName method also allows derived classes to override the event without attaching a delegate to it. A derived class must always call the OnEventName method of the base class to ensure that registered delegates receive the event. However, in a class that is sealed or NotInheritable, you can raise the event directly rather than raising it indirectly in the OnEventName method.
Consuming an Event
To consume an event in an application, you must provide an event handler (a method that handles the event) that executes program logic in response to the event and register the event handler with the event source. This process is referred to as event wiring. The exact technique used to wire events depends on the language. The following example shows the Visual Basic and C# code for an Example class that handles the AlarmEvent event and that defines an AlarmHandler method that is executed whenever the event is raised. Note that the event handler, by setting the AlarmEventArgs.Cancel property to true, stops the event from being raised repeatedly.
Public Class AlarmClock
Public Event AlarmEvent As AlarmEventHandler
Protected numberOfRings As Integer
Protected snooze As Boolean
Public Sub New(numberOfRings As Integer, snooze As Boolean)
Me.numberOfRings = numberOfRings
Me.snooze = snooze
End Sub
Public ReadOnly Property Rings As Integer
Get
Return Me.numberOfRings
End Get
End Property
Public ReadOnly Property SnoozePressed As Boolean
Get
Return Me.snooze
End Get
End Property
Protected Overridable Sub OnAlarm(ByVal e As AlarmEventArgs)
RaiseEvent AlarmEvent(Me, e)
End Sub
Public Overridable Sub Start()
Dim e As New AlarmEventArgs(snooze, numberOfRings)
Do
System.Threading.Thread.Sleep(1500)
OnAlarm(e)
Loop While Not e.Cancel
End Sub
End Class
Public Module Example
Public WithEvents alarm As AlarmClock
Private outputBlock As System.Windows.Controls.TextBlock
Dim rings As Integer
Public Sub Demo(outputBlock As System.Windows.Controls.TextBlock)
Example.outputBlock = outputBlock
alarm = New AlarmClock(3, True)
outputBlock.Text += "Alarm started..." + vbCrLf
alarm.Start()
End Sub
Private Sub AlarmHandler(sender As Object, e As AlarmEventArgs) Handles alarm.AlarmEvent
Dim totalAlarms As Integer
Do
For ring As Integer = 1 To e.Rings
outputBlock.Text += "Ring..."
Next
outputBlock.Text += vbCrLf
If totalAlarms = 3 Then e.Cancel = True
totalAlarms += 1
Loop While Not e.Cancel
End Sub
End Module
public class Example
{
private static System.Windows.Controls.TextBlock outputBlock;
private static AlarmClock alarm;
public static void Demo(System.Windows.Controls.TextBlock outputBlock)
{
Example.outputBlock = outputBlock;
alarm = new AlarmClock(3, true);
alarm.AlarmEvent += new AlarmEventHandler(Example.AlarmHandler);
outputBlock.Text += "Alarm started...\n";
alarm.Start();
}
private static void AlarmHandler(object sender, AlarmEventArgs e)
{
int totalAlarms = 0;
do {
for (int ring = 1; ring <= e.Rings; ring++)
outputBlock.Text += "Ring...";
outputBlock.Text += "\n";
if (totalAlarms == 3)
e.Cancel = true;
totalAlarms++;
} while (! e.Cancel);
}
}
Although there are numerous delegates for the various Silverlight events, as well as matching dedicated event-data classes, the event handler for a Silverlight event always references two parameters in its signature, the sender object and the event data, as described in Raising an Event previously in this topic.