Codectionary / Developer documentation / C#

Events

An event is a special kind of delegate field that provides a publish-subscribe pattern: the declaring class can raise (invoke) the event, but external code can only subscribe (+=) or unsubscribe (-=) - it cannot invoke the event directly or overwrite existing subscribers. This makes events the standard, safe way to notify other parts of a program that something has happened, like a button being clicked or a value changing.

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
    }
}
  1. Declaring, subscribing to, and raising a simple event.
  2. An event exposes subscription with += and unsubscription with -= while letting its declaring type control invocation. EventHandler receives a sender and event arguments.
  3. 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));
    }
}
  1. Defining custom EventArgs to pass extra data along with the event.
  2. An event exposes subscription with += and unsubscription with -= while letting its declaring type control invocation. EventHandler receives a sender and event arguments.
  3. 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();
}
  1. Using -= to remove a handler, important for avoiding memory leaks with long-lived publishers.
  2. An event exposes subscription with += and unsubscription with -= while letting its declaring type control invocation. EventHandler receives a sender and event arguments.
  3. 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

Specifications & further reading