Read the syntax
An event exposes subscription with += and unsubscription with -= while letting its declaring type control invocation. EventHandler receives a sender and event arguments.
The ?.Invoke form calls handlers only when subscribers exist. Keep a reference to a named handler when you will need to unsubscribe; a new lambda is a different delegate.
Syntax
public event EventHandler EventName;Examples
Basic Event with EventHandler
Declaring, subscribing to, and raising a simple event.
Button button = new Button();
button.Clicked += (sender, args) => Console.WriteLine("Handler 1: click received");
button.Clicked += (sender, args) => Console.WriteLine("Handler 2: click received");
button.Click();
public class Button
{
public event EventHandler? Clicked;
public void Click()
{
Console.WriteLine("Button was clicked");
Clicked?.Invoke(this, EventArgs.Empty); // raise the event, if anyone is subscribed
}
}- Declaring, subscribing to, and raising a simple event.
- An event exposes subscription with += and unsubscription with -= while letting its declaring type control invocation. EventHandler receives a sender and event arguments.
- Click writes the button message, then invokes both subscribed handlers. The output contains that message followed by Handler 1 and Handler 2.
Custom Event Data
Defining custom EventArgs to pass extra data along with the event.
OrderProcessor processor = new OrderProcessor();
processor.OrderPlaced += (sender, e) =>
Console.WriteLine($"Order {e.OrderId} placed for ${e.Total}");
processor.PlaceOrder("ORD-1", 49.99);
public class OrderPlacedEventArgs : EventArgs
{
public string OrderId { get; }
public double Total { get; }
public OrderPlacedEventArgs(string orderId, double total)
{
OrderId = orderId;
Total = total;
}
}
public class OrderProcessor
{
public event EventHandler<OrderPlacedEventArgs>? OrderPlaced;
public void PlaceOrder(string id, double total)
{
OrderPlaced?.Invoke(this, new OrderPlacedEventArgs(id, total));
}
}- Defining custom EventArgs to pass extra data along with the event.
- An event exposes subscription with += and unsubscription with -= while letting its declaring type control invocation. EventHandler receives a sender and event arguments.
- PlaceOrder creates custom EventArgs containing the order ID and total. The handler reads those properties and prints the order message.
Unsubscribing from an Event
Using -= to remove a handler, important for avoiding memory leaks with long-lived publishers.
Timer timer = new Timer();
void OnTick() => Console.WriteLine("Tick!");
timer.Tick += OnTick;
timer.Fire(); // Tick!
timer.Tick -= OnTick;
timer.Fire(); // nothing printed - handler was removed
public class Timer
{
public event Action? Tick;
public void Fire() => Tick?.Invoke();
}- Using -= to remove a handler, important for avoiding memory leaks with long-lived publishers.
- An event exposes subscription with += and unsubscription with -= while letting its declaring type control invocation. EventHandler receives a sender and event arguments.
- The first Fire invokes OnTick and prints Tick!. After -= removes the same handler, the second Fire has no subscriber and prints nothing.
Best practices
- Always use the null-conditional operator (Clicked?.Invoke(...)) when raising an event, since it has no effect (and won't throw) if nobody has subscribed
- Follow the standard EventHandler / EventHandler<T> pattern rather than a raw Action or Func field, so your events feel familiar to other C# developers
- Unsubscribe from events (-=) when a subscriber is no longer needed, especially for long-lived publishers - forgetting to do so is a common source of memory leaks
- Prefer events over a public delegate field for notifications - events restrict outside code to only subscribing/unsubscribing, not raising or overwriting the whole invocation list
Runtime & versions
.NET runtime. Feature availability depends on the language and runtime version used by your project; consult the official reference below.
C# language reference