Udostępnij za pośrednictwem


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.

NoteNote:

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);
       }   
    }
    
    NoteNote:

    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.