Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
---
title: "How to: Handle Multiple Events Using Event Properties"
description: Learn how to handle many events by using event properties. Define delegate collections, event keys, & event properties. Implement add & remove accessor methods.
ms.date: "03/30/2017"
title: "Handle multiple events using event properties"
description: Learn how to handle many events by using event properties. Define delegate collections, event keys, and event properties. Implement add and remove accessor methods.
ms.date: 03/25/2026
ms.topic: how-to
dev_langs:
- "csharp"
- "vb"
Expand All @@ -10,36 +11,71 @@ helpviewer_keywords:
- "multiple events [.NET]"
- "event handling [.NET], with multiple events"
- "events [.NET], multiple"
ai-usage: ai-assisted
---
# How to: Handle Multiple Events Using Event Properties

To use event properties, you define the event properties in the class that raises the events, and then set the delegates for the event properties in classes that handle the events. To implement multiple event properties in a class, the class should internally store and maintain the delegate defined for each event. For each field-like-event, a corresponding backing-field reference type is generated. This can lead to unnecessary allocations when the number of events increases. As an alternative, a common approach is to maintain an <xref:System.ComponentModel.EventHandlerList> which stores events by key.
# Handle multiple events using event properties

To store the delegates for each event, you can use the <xref:System.ComponentModel.EventHandlerList> class, or implement your own collection. The collection class must provide methods for setting, accessing, and retrieving the event handler delegate based on the event key. For example, you could use a <xref:System.Collections.Hashtable> class, or derive a custom class from the <xref:System.Collections.DictionaryBase> class. The implementation details of the delegate collection do not need to be exposed outside your class.
When a class raises many events, field-like events generate a backing-field reference type for each event. Every instance of that class carries all of those fields—even for events that are never subscribed. As the number of events grows, this per-instance overhead becomes wasteful: most fields remain `null` at runtime, yet they occupy memory in every object. Instead, use event properties backed by an <xref:System.ComponentModel.EventHandlerList>, which only allocates storage for events that have at least one subscriber.

Each event property within the class defines an add accessor method and a remove accessor method. The add accessor for an event property adds the input delegate instance to the delegate collection. The remove accessor for an event property removes the input delegate instance from the delegate collection. The event property accessors use the predefined key for the event property to add and remove instances from the delegate collection.
To store the delegates for each event, use an <xref:System.ComponentModel.EventHandlerList> or implement your own collection. The collection must provide methods for setting, accessing, and retrieving the event handler delegate based on the event key. For example, use a <xref:System.Collections.Hashtable> or a custom class derived from <xref:System.Collections.DictionaryBase>. The delegate collection's implementation details don't need to be exposed outside your class.

### To handle multiple events using event properties
Each event property defines an add accessor and a remove accessor. The add accessor adds the input delegate to the delegate collection, and the remove accessor removes it. Both accessors use a predefined key to add and remove instances from the collection.

1. Define a delegate collection within the class that raises the events.
## Prerequisites

2. Define a key for each event.
Familiarize yourself with the concepts in the [Events](index.md) article.

3. Define the event properties in the class that raises the events.
## Define multiple events using event properties

4. Use the delegate collection to implement the add and remove accessor methods for the event properties.
These steps create a `Sensor` class that exposes 10 event properties, all backed by a single `EventHandlerList`.

5. Use the public event properties to add and remove event handler delegates in the classes that handle the events.
1. Define an event data class that inherits from <xref:System.EventArgs>.

## Example
Add properties for each piece of data you want to pass to the handler:

The following C# example implements the event properties `MouseDown` and `MouseUp`, using an <xref:System.ComponentModel.EventHandlerList> to store each event's delegate. The keywords of the event property constructs are in bold type.
[!code-csharp[Conceptual.Events.Other#31](../../../samples/snippets/csharp/VS_Snippets_CLR/conceptual.events.other/cs/example3.cs#31)]
[!code-vb[Conceptual.Events.Other#31](../../../samples/snippets/visualbasic/VS_Snippets_CLR/conceptual.events.other/vb/example3.vb#31)]
:::code language="csharp" source="./snippets/how-to-handle-multiple-events-using-event-properties/csharp/Sensor.cs" id="SensorEventArgs":::
:::code language="vb" source="./snippets/how-to-handle-multiple-events-using-event-properties/vb/Sensor.vb" id="SensorEventArgs":::

## See also
1. Declare an <xref:System.ComponentModel.EventHandlerList> field to store the delegates:

:::code language="csharp" source="./snippets/how-to-handle-multiple-events-using-event-properties/csharp/Sensor.cs" id="EventHandlerListField":::
:::code language="vb" source="./snippets/how-to-handle-multiple-events-using-event-properties/vb/Sensor.vb" id="EventHandlerListField":::

1. Declare a unique key for each event.

`EventHandlerList` stores delegates by key. Use a `static readonly` object (`Shared ReadOnly` in Visual Basic) for each key—object identity ensures each key is truly unique:

:::code language="csharp" source="./snippets/how-to-handle-multiple-events-using-event-properties/csharp/Sensor.cs" id="EventKeys":::
:::code language="vb" source="./snippets/how-to-handle-multiple-events-using-event-properties/vb/Sensor.vb" id="EventKeys":::

1. Define each event as an event property with custom add and remove accessors.

Each accessor calls `AddHandler` or `RemoveHandler` on the list using that event's key. In C#, also add a protected virtual raise method that retrieves the delegate from the list by key and invokes it. In Visual Basic, the `Custom Event` declaration already includes a `RaiseEvent` block that does this:

:::code language="csharp" source="./snippets/how-to-handle-multiple-events-using-event-properties/csharp/Sensor.cs" id="SingleEventProperty":::
:::code language="vb" source="./snippets/how-to-handle-multiple-events-using-event-properties/vb/Sensor.vb" id="SingleEventProperty":::

Repeat this pattern for each event, using its corresponding key.

1. Subscribe to the event using the `+=` operator (in Visual Basic, `AddHandler`):

:::code language="csharp" source="./snippets/how-to-handle-multiple-events-using-event-properties/csharp/Program.cs" id="SubscribeEvent":::
:::code language="vb" source="./snippets/how-to-handle-multiple-events-using-event-properties/vb/Program.vb" id="SubscribeEvent":::

1. Define the event handler.

The signature must match the <xref:System.EventHandler`1> delegate—an `object` sender and your event data class as the second parameter:

:::code language="csharp" source="./snippets/how-to-handle-multiple-events-using-event-properties/csharp/Program.cs" id="HandleEvent":::
:::code language="vb" source="./snippets/how-to-handle-multiple-events-using-event-properties/vb/Program.vb" id="HandleEvent":::

The following example shows the complete `Sensor` class implementation:

:::code language="csharp" source="./snippets/how-to-handle-multiple-events-using-event-properties/csharp/Sensor.cs" id="EventProperties":::
:::code language="vb" source="./snippets/how-to-handle-multiple-events-using-event-properties/vb/Sensor.vb" id="EventProperties":::

## Related content

- <xref:System.ComponentModel.EventHandlerList?displayProperty=nameWithType>
- [Events](index.md)
- <xref:System.Web.UI.Control.Events%2A?displayProperty=nameWithType>
- [How to: Declare Custom Events To Conserve Memory](../../visual-basic/programming-guide/language-features/events/how-to-declare-custom-events-to-conserve-memory.md)
154 changes: 135 additions & 19 deletions docs/standard/events/how-to-raise-and-consume-events.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
---
title: "How to: Raise and Consume Events"
description: Raise & consume events in .NET. See examples that use the EventHandler delegate, the EventHandler<TEventArgs> delegate, & a custom delegate.
ms.date: 10/20/2025
title: "Raise and consume events"
description: Raise and consume events in .NET. See examples that use the EventHandler delegate, the EventHandler<TEventArgs> delegate, and a custom delegate.
ms.date: 03/24/2026
ms.topic: how-to
ai-usage: ai-assisted
dev_langs:
- "csharp"
- "vb"
Expand All @@ -10,33 +12,147 @@ helpviewer_keywords:
- "raising events"
- "events [.NET], samples"
---
# How to: Raise and consume events

The examples in this article show how to work with events. They include examples of the <xref:System.EventHandler> delegate, the <xref:System.EventHandler`1> delegate, and a custom delegate to illustrate events with and without data.
# Raise and consume events

The examples use concepts described in the [Events](index.md) article.
This article shows how to work with events in .NET using the <xref:System.EventHandler> delegate, the <xref:System.EventHandler`1> delegate, and a custom delegate, with examples for events with and without data.

## Example 1
## Prerequisites

This first example shows how to raise and consume an event that doesn't have data. It contains a class named `Counter` that has an event called `ThresholdReached`. This event is raised when a counter value equals or exceeds a threshold value. The <xref:System.EventHandler> delegate is associated with the event because no event data is provided.
Familiarize yourself with the concepts in the [Events](index.md) article.

[!code-csharp[EventsOverview#5](./snippets/raise-consume/csharp/programnodata.cs#5)]
[!code-vb[EventsOverview#5](./snippets/raise-consume/vb/module1nodata.vb#5)]
## Raise an event without data

## Example 2
These steps create a `Counter` class that fires a `ThresholdReached` event when a running total reaches or exceeds a threshold.

This second example shows how to raise and consume an event that provides data. The <xref:System.EventHandler`1> delegate is associated with the event, and an instance of a custom event data object is provided.
1. Declare the event using the <xref:System.EventHandler> delegate.

[!code-csharp[EventsOverview#6](./snippets/raise-consume/csharp/programwithdata.cs#6)]
[!code-vb[EventsOverview#6](./snippets/raise-consume/vb/module1withdata.vb#6)]
Use `EventHandler` when your event doesn't pass data to the handler:

## Example 3
:::code language="csharp" source="./snippets/how-to-raise-and-consume-events/csharp/EventNoData.cs" id="DeclareEvent":::
:::code language="vb" source="./snippets/how-to-raise-and-consume-events/vb/EventNoData.vb" id="DeclareEvent":::

This third example shows how to declare a delegate for an event. The delegate is named `ThresholdReachedEventHandler`. This example is just an illustration. Typically, you don't have to declare a delegate for an event because you can use either the <xref:System.EventHandler> or the <xref:System.EventHandler`1> delegate. You should declare a delegate only in rare scenarios, such as making your class available to legacy code that can't use generics.
1. Add a protected virtual method (`Protected Overridable` in Visual Basic) to raise the event.

[!code-csharp[EventsOverview#7](./snippets/raise-consume/csharp/programwithdelegate.cs#7)]
[!code-vb[EventsOverview#7](./snippets/raise-consume/vb/module1withdelegate.vb#7)]
This pattern lets derived classes override the event-raising behavior without directly invoking the delegate. In C#, use the null-conditional operator (`?.`) to guard against no subscribers (in Visual Basic, `RaiseEvent` handles this automatically):

## See also
:::code language="csharp" source="./snippets/how-to-raise-and-consume-events/csharp/EventNoData.cs" id="RaiseEventMethod":::
:::code language="vb" source="./snippets/how-to-raise-and-consume-events/vb/EventNoData.vb" id="RaiseEventMethod":::

1. Call the raise method when the condition is met.

Pass <xref:System.EventArgs.Empty> because this event carries no data:

:::code language="csharp" source="./snippets/how-to-raise-and-consume-events/csharp/EventNoData.cs" id="RaiseEvent":::
:::code language="vb" source="./snippets/how-to-raise-and-consume-events/vb/EventNoData.vb" id="RaiseEvent":::

1. Subscribe to the event using the `+=` operator (in Visual Basic, `AddHandler`):

:::code language="csharp" source="./snippets/how-to-raise-and-consume-events/csharp/EventNoData.cs" id="SubscribeEvent":::
:::code language="vb" source="./snippets/how-to-raise-and-consume-events/vb/EventNoData.vb" id="SubscribeEvent":::

1. Define the event handler method.

Its signature must match the <xref:System.EventHandler> delegate—the first parameter is the event source and the second is <xref:System.EventArgs>:

:::code language="csharp" source="./snippets/how-to-raise-and-consume-events/csharp/EventNoData.cs" id="HandleEvent":::
:::code language="vb" source="./snippets/how-to-raise-and-consume-events/vb/EventNoData.vb" id="HandleEvent":::

The following example shows the complete implementation:

:::code language="csharp" source="./snippets/how-to-raise-and-consume-events/csharp/EventNoData.cs" id="ThresholdReachedNoData":::
:::code language="vb" source="./snippets/how-to-raise-and-consume-events/vb/EventNoData.vb" id="ThresholdReachedNoData":::

## Raise an event with data

These steps extend the previous `Counter` example to raise an event that includes data—the threshold value and the time it was reached.

1. Define an event data class that inherits from <xref:System.EventArgs>.

Add properties for each piece of data you want to pass to the handler:

:::code language="csharp" source="./snippets/how-to-raise-and-consume-events/csharp/EventWithData.cs" id="EventDataClass2":::
:::code language="vb" source="./snippets/how-to-raise-and-consume-events/vb/EventWithData.vb" id="EventDataClass2":::

1. Declare the event using the <xref:System.EventHandler`1> delegate, passing your event data class as the type argument:

:::code language="csharp" source="./snippets/how-to-raise-and-consume-events/csharp/EventWithData.cs" id="DeclareEvent2":::
:::code language="vb" source="./snippets/how-to-raise-and-consume-events/vb/EventWithData.vb" id="DeclareEvent2":::

1. Add a protected virtual method (`Protected Overridable` in Visual Basic) to raise the event.

This pattern lets derived classes override the event-raising behavior without directly invoking the delegate. In C#, use the null-conditional operator (`?.`) to guard against no subscribers (in Visual Basic, `RaiseEvent` handles this automatically):

:::code language="csharp" source="./snippets/how-to-raise-and-consume-events/csharp/EventWithData.cs" id="RaiseEventMethod2":::
:::code language="vb" source="./snippets/how-to-raise-and-consume-events/vb/EventWithData.vb" id="RaiseEventMethod2":::

1. Populate the event data object and call the raise method when the condition is met:

:::code language="csharp" source="./snippets/how-to-raise-and-consume-events/csharp/EventWithData.cs" id="RaiseEvent2":::
:::code language="vb" source="./snippets/how-to-raise-and-consume-events/vb/EventWithData.vb" id="RaiseEvent2":::

1. Subscribe to the event using the `+=` operator (in Visual Basic, `AddHandler`):

:::code language="csharp" source="./snippets/how-to-raise-and-consume-events/csharp/EventWithData.cs" id="SubscribeEvent2":::
:::code language="vb" source="./snippets/how-to-raise-and-consume-events/vb/EventWithData.vb" id="SubscribeEvent2":::

1. Define the event handler.

The second parameter type is `ThresholdReachedEventArgs` instead of <xref:System.EventArgs>, which lets the handler read the event data:

:::code language="csharp" source="./snippets/how-to-raise-and-consume-events/csharp/EventWithData.cs" id="HandleEvent2":::
:::code language="vb" source="./snippets/how-to-raise-and-consume-events/vb/EventWithData.vb" id="HandleEvent2":::

The following example shows the complete implementation:

:::code language="csharp" source="./snippets/how-to-raise-and-consume-events/csharp/EventWithData.cs" id="ThresholdReachedWithData":::
:::code language="vb" source="./snippets/how-to-raise-and-consume-events/vb/EventWithData.vb" id="ThresholdReachedWithData":::

## Declare a custom delegate for an event

Declare a custom delegate only in rare scenarios, such as making your class available to legacy code that can't use generics. For most cases, use <xref:System.EventHandler`1> as shown in the previous section.

1. Declare the custom delegate type.

The delegate signature must match the event handler signature—two parameters: the event source (`object`; in Visual Basic, `Object`) and the event data class:

:::code language="csharp" source="./snippets/how-to-raise-and-consume-events/csharp/EventWithDelegate.cs" id="DeclareDelegateType":::
:::code language="vb" source="./snippets/how-to-raise-and-consume-events/vb/EventWithDelegate.vb" id="DeclareDelegateType":::

1. Declare the event using your custom delegate type instead of <xref:System.EventHandler`1>:

:::code language="csharp" source="./snippets/how-to-raise-and-consume-events/csharp/EventWithDelegate.cs" id="DeclareEventWithDelegate":::
:::code language="vb" source="./snippets/how-to-raise-and-consume-events/vb/EventWithDelegate.vb" id="DeclareEventWithDelegate":::

1. Add a protected virtual method (`Protected Overridable` in Visual Basic) to raise the event.

In C#, use the null-conditional operator (`?.`) to guard against no subscribers (in Visual Basic, `RaiseEvent` handles this automatically):

:::code language="csharp" source="./snippets/how-to-raise-and-consume-events/csharp/EventWithDelegate.cs" id="RaiseEventMethodDelegate":::
:::code language="vb" source="./snippets/how-to-raise-and-consume-events/vb/EventWithDelegate.vb" id="RaiseEventMethodDelegate":::

1. Populate the event data object and call the raise method when the condition is met:

:::code language="csharp" source="./snippets/how-to-raise-and-consume-events/csharp/EventWithDelegate.cs" id="RaiseEventDelegate":::
:::code language="vb" source="./snippets/how-to-raise-and-consume-events/vb/EventWithDelegate.vb" id="RaiseEventDelegate":::

1. Subscribe to the event using the `+=` operator (in Visual Basic, `AddHandler`):

:::code language="csharp" source="./snippets/how-to-raise-and-consume-events/csharp/EventWithDelegate.cs" id="SubscribeEventDelegate":::
:::code language="vb" source="./snippets/how-to-raise-and-consume-events/vb/EventWithDelegate.vb" id="SubscribeEventDelegate":::

1. Define the event handler.

The handler signature must match the custom delegate—`object` for the sender and your event data class for the second parameter:

:::code language="csharp" source="./snippets/how-to-raise-and-consume-events/csharp/EventWithDelegate.cs" id="HandleEventDelegate":::
:::code language="vb" source="./snippets/how-to-raise-and-consume-events/vb/EventWithDelegate.vb" id="HandleEventDelegate":::

The following example shows the complete implementation:

:::code language="csharp" source="./snippets/how-to-raise-and-consume-events/csharp/EventWithDelegate.cs" id="ThresholdReachedWithDelegate":::
:::code language="vb" source="./snippets/how-to-raise-and-consume-events/vb/EventWithDelegate.vb" id="ThresholdReachedWithDelegate":::

## Related content

- [Events](index.md)
Loading
Loading